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 @@ - +
+
+
+
+
BLUE_E
BLUE_D
BLUE_C
BLUE_B
BLUE_A
TEAL_E
TEAL_D
TEAL_C
TEAL_B
TEAL_A
GREEN_E
GREEN_D
GREEN_C
GREEN_B
GREEN_A
YELLOW_E
YELLOW_D
YELLOW_C
YELLOW_B
YELLOW_A
GOLD_E
GOLD_D
GOLD_C
GOLD_B
GOLD_A
RED_E
RED_D
RED_C
RED_B
RED_A
MAROON_E
MAROON_D
MAROON_C
MAROON_B
MAROON_A
PURPLE_E
PURPLE_D
PURPLE_C
PURPLE_B
PURPLE_A
GREY_E
GREY_D
GREY_C
GREY_B
GREY_A
WHITE
BLACK
GREY_BROWN
DARK_BROWN
LIGHT_BROWN
PINK
LIGHT_PINK
GREEN_SCREEN
ORANGE
.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\xb4x\xe1\xbf\xa2\x80&\x01i\x0c\xd0\xbf\x8c\xe0u\xf4M[\xee?\x85\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?\x9b^!\x88\xda?\xaf\x84&\xb9a;\xea\xbf\xff\xfe\xe4\x8e\xbd\xdf\x02\xc06G.\xdc4>\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\x9fb\xd0?\xbe\xac-\xc1k\x16\xe7?\xd7u\xc4\xfa\xb0y\xdb\xbf`\xd4\xb1\xe2\x1a)\x00\xc0\xef_\xb8\x87\x87\xe6\xe3\xbf\x05\x7f\r\xd4\xbe\xca\xda\xbf\xb7h\xc9\xc4\x90\xd2\xd3?\x82\xb0\xbaJL\x80\xf0\xbf\x83\x9a\x80\x1c\x06\x16\x05\xc0*9.\xa2+?\xed\xbf\t,T\x0e\x08\x83\xf7\xbfs\x1b\xc5K\x8e\xb2\xf5\xbfr\xe3\t\x88\xf3\r\xd1\xbf\x1c\r\x07\x02.\xc6\xf2\xbf\xf7^\x1ak\xc2\x06\xf6\xbf\xa9dq}\xb3\xc5\xd0?X\xe3\xfb\xeb4\x0e\xf6?,\x94\x99\x8c\x86&\xc6?5to)\x162\x07@L\x14\xe4\x92\xcf\xa7\xe5\xbf\x8a\xdd\xa4$k\xde\xfc?\xd2\x88\xbe\xd3\x80<\x00\xc0_\xde1\xf4\x1a\xb5\xf2\xbfo\x11\x0f{\xaa\x17\xea?0\xc8Z\xf7>\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?\xb2\xb5?\xfb\x9c\x80\xb9\xe2\xa6\xf9\xbf\xd5v\xc0\xfd\xae\x00\xe6\xbf\x81\x81Ak\x84\xf8\x06@,\x9dy\x9b\'Q\xfc\xbfSl\x10\x1a\x87\x1f\x03\xc0\x82\xc5h\xff\xe7\x14\xe4?xm\xcf^\xb5N\xc2\xbfp\x05\x08\xbb\xd8<\xff\xbf\xcc\x9cS\xfa\xbf}\xe4\xbf\x15J\xf1\xed\xe0J\x00\xc0\xb5\x92\xbce\x95\xdf\xe6?\xec\xcb\x07vH\xf9\x87\xbf\x10\xc1\xbc\xa5\x0ft\xf4?\xf3\x12\xbf\xd0\x7f\x9d\x01@\xb5\xa0\x83\x99)\xa0\xcc?\xc6K\x85Lhh\xd0\xbf\xf8\xb6\xf4v#\x92\xf1\xbf\xe2\xf1\xc0\xdb{\xe7\xd5?\xed\xeaH\xfe\x86\x8f\xd7?,\x11\x00\xda\xd0\xe9\xd7?N[\xb5\x05.V\xef?\x1cb@\x88\xce\x96\xd9?8\x9a>=_\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\xe0?)\xec(\xa2\xfe\x10\xf5?4\xf1\xeb\xdc\xde)\xf7\xbf0\xb1\xc6-\xc9\x83\xb2?j#x\xffu\xc2\xe1?\xc5\xef\x94\xbc\xa8`\xeb?\x7f\xd9\xaf\x9a\xbd\xce\xe9\xbf\xf0\x98\x9e]d\xa1\xf0\xbf\xadh\xc2\xe4\xc0\xa9\xf8\xbf\xb2\x98\xae\xbe\xe3-\xbb\xbfu\xe2\xa9\x17F\x86\xf2?\x06\x97\x03\x139{\xbe?\xe5\xb1E\xcf%p\xeb?e\xdeLa \xf9?\xab\x88\xb4\xb5-\x1c\xf0?\xd00q_K9\xf6\xbf\xd7`\xa1N\x86O\xec\xbf\xc6!\x98t9U\xf1?\xda\x15\xddz8\x9d\xca\xbfy\x8d\xd9^^\x12\xf2\xbf}\xe3\x00\xc8*\xac\xed?3\xb9`\x1f\xa1\x12\xe2?B\n\xdd\x1e\xad,\xf3?\xf2\xa4\xcfe\xb2$\xf7\xbfg:\xacR\xa0e\xea\xbf\xf0l\x93\x86j[\x03\xc0-\xfb\x0f\xc0\xd1\xf6\xd4\xbf\xe0\xcb/m\xd1\xed\xeb?\x9d^\xc6w\xcc(\x05\xc0&4X\xf1\xe5|\xe4\xbf\xe4\x8d\xe3\x88\x1eU\xdb?_\r\xf4\xc4"\xc3\xe1?\x11\nr%\xb3 \x02@B\'\xe79U\x1f\xf6\xbf\xdd8\x0f\xf0(\x03\xe5\xbf\x02]\x08i\xd7\x8d\xea\xbfx\xdf\x9ek\x16h\xc3\xbf\xa3V\x9bj\xd7\xe2\xd7\xbf\xf3\xb4\x01\x8e\xbd\xc8\xe9?\xfb/!Z+r\xbe?\xb6\xe8\xb6\x19\x9d^\xf5\xbf\x10\x8f54o\xb9\xdf?\x83b\xed\x9e:\x99\xfc?m^\'l\xd0\x1b\xf6?!W\xccj\x0e\x9c\xd5?X1\xd2\x14\xe0\xc7\xcd\xbf2\xd6s\x83\x9e\x8d\xe2?\xd5^Zo\x14X\xee\xbf\x17\x0f\x1f\x18\xdd\x8a\xfd?~\x06t\xb8\x9bl\xe2?d\xcc\xf6\xf3\x7f\x86\xee\xbf\x12\xc8\x8b\x9f*t\xfa\xbf\xac\x0c\xce\xa0L\x0b\xd5?\x95\xf2\x80\x8f\xc8\xe0\xe2\xbf\x0fh\xdef`\x81\xf8?}\xa0\xbb\xae%j\xbb\xbf\xb9|8/\xd5\x9d\xe9\xbf>\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*\xdb\xbf5\xb7\xec\xf4o9\xea?|\xa8\x18BA\x80\xf2\xbfM\xc01\x9e\x0c\xd4\xef\xbf\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