diff --git a/LICENSE b/LICENSE index 9a09471ec0..be67c8db0a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,4 @@ -All files of this project under the directories active_projects and old_projects -are copyright 3Blue1Brown LLC and used by permission for this project only. +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: diff --git a/README.md b/README.md index 2a05408b9f..0e7b845172 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ - +![logo](logo/cropped.png) [![Build Status](https://travis-ci.org/3b1b/manim.svg?branch=master)](https://travis-ci.org/3b1b/manim) -[![Documentation](https://img.shields.io/badge/docs-EulerTour-blue.svg)](https://www.eulertour.com/learn/manim/) +[![Documentation](https://img.shields.io/badge/docs-EulerTour-blue.svg)](https://www.eulertour.com/docs) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/) [![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit)](https://www.reddit.com/r/manim/) [![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord)](https://discord.gg/mMRrZQW) @@ -9,7 +9,7 @@ Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos at [3Blue1Brown](https://www.3blue1brown.com/). ## Installation -Manim runs on python 3.7. You can install it from PyPI via pip +Manim runs on Python 3.7. You can install it from PyPI via pip: ```sh pip3 install manimlib @@ -23,7 +23,7 @@ You can now use it via the `manim` command. For example: manim my_project.py MyScene ``` -For more options, take a look at the “Using manim“ sections further below. +For more options, take a look at the [Using manim](#using-manim) sections further below. ### Directly @@ -39,7 +39,7 @@ python3 ./manim.py example_scenes.py SquareToCircle -pl ### Directly (Windows) 1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows). -2. Install Cairo. Download the wheel from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo. For most users, ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` will do fine. +2. [Install Cairo](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo). For most users, ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` will do fine. ```sh pip3 install C:\path\to\wheel\pycairo‑1.18.0‑cp37‑cp37m‑win32.whl ``` @@ -81,7 +81,7 @@ environment variable to the absolute path containing your scene file and the 1. [Install Docker](https://docs.docker.com) 2. [Install Docker Compose](https://docs.docker.com/compose/install/) -3. Render an animation +3. Render an animation: ```sh INPUT_PATH=/path/to/dir/containing/source/code \ OUTPUT_PATH=/path/to/output/ \ @@ -91,9 +91,9 @@ The command needs to be run as root if your username is not in the docker group. You can replace `example.scenes.py` with any relative path from your `INPUT_PATH`. - +![docker diagram](./manim_docker_diagram.png) -After running the output will say files ready at `/tmp/output/`, which refers to path inside the container. Your OUTPUT_PATH is bind mounted to this `/tmp/output` so any changes made by the container to `/tmp/output` will be mirrored on your OUTPUT_PATH. `/media/` will be created in `OUTPUT_PATH`. +After running the output will say files ready at `/tmp/output/`, which refers to path inside the container. Your `OUTPUT_PATH` is bind mounted to this `/tmp/output` so any changes made by the container to `/tmp/output` will be mirrored on your `OUTPUT_PATH`. `/media/` will be created in `OUTPUT_PATH`. `-p` won't work as manim would look for video player in the container system, which it does not have. @@ -108,49 +108,26 @@ python3 -m manim example_scenes.py SquareToCircle -pl The `-p` flag in the command above is for previewing, meaning the video file will automatically open when it is done rendering. The `-l` flag is for a faster rendering at a lower quality. Some other useful flags include: - * `-s` to skip to the end and just show the final frame. * `-n ` to skip ahead to the `n`'th animation of a scene. * `-f` to show the file in finder (for OSX). Set `MEDIA_DIR` environment variable to specify where the image and animation files will be written. -Look through the `old_projects` folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project. +Look through the `old_projects` folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project. -While developing a scene, the `-sp` flags are helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the `-n` flag to skip over some number of animations. +While developing a scene, the `-sp` flags are helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the `-n` flag to skip over some number of animations. ### Documentation -Documentation is in progress at [eulertour.com/learn/manim](https://www.eulertour.com/learn/manim/). +Documentation is in progress at [eulertour.com/docs](https://www.eulertour.com/docs/). ### Walkthrough -Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/) on getting started with manim, which has been updated to run on python 3.7. - -### Live Streaming -To live stream your animations, simply run manim with the `--livestream` option. - -```sh -> python -m manim --livestream -Writing to media/videos/scene/scene/1080p30/LiveStreamTemp.mp4 - -Manim is now running in streaming mode. Stream animations by passing -them to manim.play(), e.g. ->>> c = Circle() ->>> manim.play(ShowCreation(c)) - ->>> -``` - -It is also possible to stream directly to Twitch. To do that simply pass ---livestream and --to-twitch to manim and specify the stream key with ---with-key. Then when you follow the above example the stream will directly -start on your Twitch channel (with no audio support). - +Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/) on getting started with manim, which has been updated to run on Python 3.7. ## Contributing Is always welcome. In particular, there is a dire need for tests and documentation. - ## License -All files in the directories active_projects and old_projects, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown. +All files in the directory `from_3b1b`, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown. The general purpose animation code found in the remainder of the repository, on the other hand, is under the MIT license. diff --git a/docs/source/constants.rst b/docs/source/constants.rst index 2102da08a6..7bc34d1473 100644 --- a/docs/source/constants.rst +++ b/docs/source/constants.rst @@ -18,7 +18,7 @@ Directories Created under ``MEDIA_DIR`` by default. TEX_DIR Files written by Latex are stored here. It also acts as a cache - so that the files aren't rewritten each Latex is needed. + so that the files aren't rewritten each time Latex is needed. Those directories are created if they don't exist. diff --git a/docs/source/installation/windows.rst b/docs/source/installation/windows.rst index 5f6a836b38..e58d125c7d 100644 --- a/docs/source/installation/windows.rst +++ b/docs/source/installation/windows.rst @@ -49,12 +49,12 @@ 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 requirement.txt`` +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 installtion process you should see the help text. +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/from_3b1b/active/bayes/footnote.py b/from_3b1b/active/bayes/footnote.py new file mode 100644 index 0000000000..82449f2a4c --- /dev/null +++ b/from_3b1b/active/bayes/footnote.py @@ -0,0 +1,1152 @@ +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 new file mode 100644 index 0000000000..1a8c6c062e --- /dev/null +++ b/from_3b1b/active/bayes/part1.py @@ -0,0 +1,4830 @@ +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_critical_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/active_projects/diffyq/all_part1_scenes.py b/from_3b1b/active/diffyq/all_part1_scenes.py similarity index 100% rename from active_projects/diffyq/all_part1_scenes.py rename to from_3b1b/active/diffyq/all_part1_scenes.py diff --git a/active_projects/diffyq/all_part2_scenes.py b/from_3b1b/active/diffyq/all_part2_scenes.py similarity index 100% rename from active_projects/diffyq/all_part2_scenes.py rename to from_3b1b/active/diffyq/all_part2_scenes.py diff --git a/active_projects/diffyq/all_part3_scenes.py b/from_3b1b/active/diffyq/all_part3_scenes.py similarity index 100% rename from active_projects/diffyq/all_part3_scenes.py rename to from_3b1b/active/diffyq/all_part3_scenes.py diff --git a/active_projects/diffyq/all_part4_scenes.py b/from_3b1b/active/diffyq/all_part4_scenes.py similarity index 100% rename from active_projects/diffyq/all_part4_scenes.py rename to from_3b1b/active/diffyq/all_part4_scenes.py diff --git a/active_projects/diffyq/all_part5_scenes.py b/from_3b1b/active/diffyq/all_part5_scenes.py similarity index 100% rename from active_projects/diffyq/all_part5_scenes.py rename to from_3b1b/active/diffyq/all_part5_scenes.py diff --git a/active_projects/diffyq/fourier_montage_scenes.py b/from_3b1b/active/diffyq/fourier_montage_scenes.py similarity index 100% rename from active_projects/diffyq/fourier_montage_scenes.py rename to from_3b1b/active/diffyq/fourier_montage_scenes.py diff --git a/active_projects/diffyq/part1/pendulum.py b/from_3b1b/active/diffyq/part1/pendulum.py similarity index 99% rename from active_projects/diffyq/part1/pendulum.py rename to from_3b1b/active/diffyq/part1/pendulum.py index ffc6dc2ff6..97cf34c765 100644 --- a/active_projects/diffyq/part1/pendulum.py +++ b/from_3b1b/active/diffyq/part1/pendulum.py @@ -254,7 +254,7 @@ class ThetaVsTAxes(Axes): "tick_frequency": PI / 8, "unit_size": 1.5, }, - "number_line_config": { + "axis_config": { "color": "#EEEEEE", "stroke_width": 2, "include_tip": False, @@ -371,7 +371,7 @@ class IntroducePendulum(PiCreatureScene, MovingCameraScene): "tip_length": 0.3, }, "x_max": 12, - "number_line_config": { + "axis_config": { "stroke_width": 2, } }, @@ -750,7 +750,7 @@ class LowAnglePendulum(Scene): "number_scale_val": 0.5, }, "x_max": 25, - "number_line_config": { + "axis_config": { "tip_length": 0.3, "stroke_width": 2, } @@ -837,7 +837,7 @@ class MediumAnglePendulum(LowAnglePendulum): "y_axis_config": {"unit_size": 0.75}, "y_max": PI / 2, "y_min": -PI / 2, - "number_line_config": { + "axis_config": { "tip_length": 0.3, "stroke_width": 2, } @@ -867,7 +867,7 @@ class HighAnglePendulum(LowAnglePendulum): "y_axis_config": {"unit_size": 0.5}, "y_max": PI, "y_min": -PI, - "number_line_config": { + "axis_config": { "tip_length": 0.3, "stroke_width": 2, } @@ -888,7 +888,7 @@ class VeryLowAnglePendulum(LowAnglePendulum): "y_axis_config": {"unit_size": 2}, "y_max": PI / 4, "y_min": -PI / 4, - "number_line_config": { + "axis_config": { "tip_length": 0.3, "stroke_width": 2, } diff --git a/active_projects/diffyq/part1/phase_space.py b/from_3b1b/active/diffyq/part1/phase_space.py similarity index 100% rename from active_projects/diffyq/part1/phase_space.py rename to from_3b1b/active/diffyq/part1/phase_space.py diff --git a/active_projects/diffyq/part1/pi_scenes.py b/from_3b1b/active/diffyq/part1/pi_scenes.py similarity index 100% rename from active_projects/diffyq/part1/pi_scenes.py rename to from_3b1b/active/diffyq/part1/pi_scenes.py diff --git a/active_projects/diffyq/part1/shared_constructs.py b/from_3b1b/active/diffyq/part1/shared_constructs.py similarity index 100% rename from active_projects/diffyq/part1/shared_constructs.py rename to from_3b1b/active/diffyq/part1/shared_constructs.py diff --git a/active_projects/diffyq/part1/staging.py b/from_3b1b/active/diffyq/part1/staging.py similarity index 99% rename from active_projects/diffyq/part1/staging.py rename to from_3b1b/active/diffyq/part1/staging.py index 1ab1a01e9a..94ca053e80 100644 --- a/active_projects/diffyq/part1/staging.py +++ b/from_3b1b/active/diffyq/part1/staging.py @@ -3,8 +3,8 @@ 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 old_projects.div_curl import PhaseSpaceOfPopulationModel -from old_projects.div_curl import ShowTwoPopulations +from from_3b1b.old.div_curl import PhaseSpaceOfPopulationModel +from from_3b1b.old.div_curl import ShowTwoPopulations # Scenes diff --git a/active_projects/diffyq/part1/wordy_scenes.py b/from_3b1b/active/diffyq/part1/wordy_scenes.py similarity index 100% rename from active_projects/diffyq/part1/wordy_scenes.py rename to from_3b1b/active/diffyq/part1/wordy_scenes.py diff --git a/active_projects/diffyq/part2/fourier_series.py b/from_3b1b/active/diffyq/part2/fourier_series.py similarity index 99% rename from active_projects/diffyq/part2/fourier_series.py rename to from_3b1b/active/diffyq/part2/fourier_series.py index b734e7d317..8ca76b2840 100644 --- a/active_projects/diffyq/part2/fourier_series.py +++ b/from_3b1b/active/diffyq/part2/fourier_series.py @@ -372,18 +372,18 @@ def get_path(self): return path -class FourierOfName(FourierOfPiSymbol, MovingCameraScene): +class FourierOfTexPaths(FourierOfPiSymbol, MovingCameraScene): CONFIG = { "n_vectors": 100, "name_color": WHITE, - "name_text": "Abc", + "animated_name": "Abc", "time_per_symbol": 5, "slow_factor": 1 / 5, "parametric_function_step_size": 0.01, } def construct(self): - name = TextMobject(self.name_text) + name = TextMobject(self.animated_name) max_width = FRAME_WIDTH - 2 max_height = FRAME_HEIGHT - 2 name.set_width(max_width) diff --git a/active_projects/diffyq/part2/heat_equation.py b/from_3b1b/active/diffyq/part2/heat_equation.py similarity index 100% rename from active_projects/diffyq/part2/heat_equation.py rename to from_3b1b/active/diffyq/part2/heat_equation.py diff --git a/active_projects/diffyq/part2/pi_scenes.py b/from_3b1b/active/diffyq/part2/pi_scenes.py similarity index 100% rename from active_projects/diffyq/part2/pi_scenes.py rename to from_3b1b/active/diffyq/part2/pi_scenes.py diff --git a/active_projects/diffyq/part2/shared_constructs.py b/from_3b1b/active/diffyq/part2/shared_constructs.py similarity index 100% rename from active_projects/diffyq/part2/shared_constructs.py rename to from_3b1b/active/diffyq/part2/shared_constructs.py diff --git a/active_projects/diffyq/part2/staging.py b/from_3b1b/active/diffyq/part2/staging.py similarity index 99% rename from active_projects/diffyq/part2/staging.py rename to from_3b1b/active/diffyq/part2/staging.py index 48d02d06a8..234ecd326f 100644 --- a/active_projects/diffyq/part2/staging.py +++ b/from_3b1b/active/diffyq/part2/staging.py @@ -235,7 +235,7 @@ def add_graph(self): x_max=20, y_min=0, y_max=10, - number_line_config={ + axis_config={ "unit_size": 0.5, }, ) diff --git a/active_projects/diffyq/part2/wordy_scenes.py b/from_3b1b/active/diffyq/part2/wordy_scenes.py similarity index 100% rename from active_projects/diffyq/part2/wordy_scenes.py rename to from_3b1b/active/diffyq/part2/wordy_scenes.py diff --git a/active_projects/diffyq/part3/discrete_case.py b/from_3b1b/active/diffyq/part3/discrete_case.py similarity index 100% rename from active_projects/diffyq/part3/discrete_case.py rename to from_3b1b/active/diffyq/part3/discrete_case.py diff --git a/active_projects/diffyq/part3/pi_creature_scenes.py b/from_3b1b/active/diffyq/part3/pi_creature_scenes.py similarity index 100% rename from active_projects/diffyq/part3/pi_creature_scenes.py rename to from_3b1b/active/diffyq/part3/pi_creature_scenes.py diff --git a/active_projects/diffyq/part3/staging.py b/from_3b1b/active/diffyq/part3/staging.py similarity index 99% rename from active_projects/diffyq/part3/staging.py rename to from_3b1b/active/diffyq/part3/staging.py index baa5dad86d..fc917a4913 100644 --- a/active_projects/diffyq/part3/staging.py +++ b/from_3b1b/active/diffyq/part3/staging.py @@ -234,7 +234,7 @@ class FourierSeriesIllustraiton(Scene): CONFIG = { "n_range": range(1, 31, 2), "axes_config": { - "number_line_config": { + "axis_config": { "include_tip": False, }, "x_axis_config": { diff --git a/active_projects/diffyq/part3/temperature_graphs.py b/from_3b1b/active/diffyq/part3/temperature_graphs.py similarity index 100% rename from active_projects/diffyq/part3/temperature_graphs.py rename to from_3b1b/active/diffyq/part3/temperature_graphs.py diff --git a/active_projects/diffyq/part3/wordy_scenes.py b/from_3b1b/active/diffyq/part3/wordy_scenes.py similarity index 100% rename from active_projects/diffyq/part3/wordy_scenes.py rename to from_3b1b/active/diffyq/part3/wordy_scenes.py diff --git a/active_projects/diffyq/part4/complex_functions.py b/from_3b1b/active/diffyq/part4/complex_functions.py similarity index 100% rename from active_projects/diffyq/part4/complex_functions.py rename to from_3b1b/active/diffyq/part4/complex_functions.py diff --git a/active_projects/diffyq/part4/fourier_series_scenes.py b/from_3b1b/active/diffyq/part4/fourier_series_scenes.py similarity index 100% rename from active_projects/diffyq/part4/fourier_series_scenes.py rename to from_3b1b/active/diffyq/part4/fourier_series_scenes.py diff --git a/active_projects/diffyq/part4/long_fourier_scenes.py b/from_3b1b/active/diffyq/part4/long_fourier_scenes.py similarity index 100% rename from active_projects/diffyq/part4/long_fourier_scenes.py rename to from_3b1b/active/diffyq/part4/long_fourier_scenes.py diff --git a/active_projects/diffyq/part4/pi_creature_scenes.py b/from_3b1b/active/diffyq/part4/pi_creature_scenes.py similarity index 100% rename from active_projects/diffyq/part4/pi_creature_scenes.py rename to from_3b1b/active/diffyq/part4/pi_creature_scenes.py diff --git a/active_projects/diffyq/part4/staging.py b/from_3b1b/active/diffyq/part4/staging.py similarity index 100% rename from active_projects/diffyq/part4/staging.py rename to from_3b1b/active/diffyq/part4/staging.py diff --git a/active_projects/diffyq/part4/temperature_scenes.py b/from_3b1b/active/diffyq/part4/temperature_scenes.py similarity index 100% rename from active_projects/diffyq/part4/temperature_scenes.py rename to from_3b1b/active/diffyq/part4/temperature_scenes.py diff --git a/active_projects/diffyq/part4/three_d_graphs.py b/from_3b1b/active/diffyq/part4/three_d_graphs.py similarity index 100% rename from active_projects/diffyq/part4/three_d_graphs.py rename to from_3b1b/active/diffyq/part4/three_d_graphs.py diff --git a/active_projects/diffyq/part5/staging.py b/from_3b1b/active/diffyq/part5/staging.py similarity index 100% rename from active_projects/diffyq/part5/staging.py rename to from_3b1b/active/diffyq/part5/staging.py diff --git a/active_projects/diffyq/solve_pendulum_ode_sample_code.py b/from_3b1b/active/diffyq/solve_pendulum_ode_sample_code.py similarity index 100% rename from active_projects/diffyq/solve_pendulum_ode_sample_code.py rename to from_3b1b/active/diffyq/solve_pendulum_ode_sample_code.py diff --git a/old_projects/256.py b/from_3b1b/old/256.py similarity index 99% rename from old_projects/256.py rename to from_3b1b/old/256.py index b2dd052623..9e22af8734 100644 --- a/old_projects/256.py +++ b/from_3b1b/old/256.py @@ -1,6 +1,6 @@ from manimlib.imports import * -from old_projects.crypto import sha256_tex_mob, bit_string_to_mobject, BitcoinLogo +from from_3b1b.old.crypto import sha256_tex_mob, bit_string_to_mobject, BitcoinLogo def get_google_logo(): result = SVGMobject( diff --git a/old_projects/WindingNumber.py b/from_3b1b/old/WindingNumber.py similarity index 100% rename from old_projects/WindingNumber.py rename to from_3b1b/old/WindingNumber.py diff --git a/old_projects/WindingNumber_G.py b/from_3b1b/old/WindingNumber_G.py similarity index 99% rename from old_projects/WindingNumber_G.py rename to from_3b1b/old/WindingNumber_G.py index f1b1b4ed9a..b0fb62012a 100644 --- a/old_projects/WindingNumber_G.py +++ b/from_3b1b/old/WindingNumber_G.py @@ -2,8 +2,8 @@ from manimlib.imports import * -from old_projects.uncertainty import Flash -from old_projects.WindingNumber import * +from from_3b1b.old.uncertainty import Flash +from from_3b1b.old.WindingNumber import * # Warning, this file uses ContinualChangingDecimal, diff --git a/old_projects/alt_calc.py b/from_3b1b/old/alt_calc.py similarity index 99% rename from old_projects/alt_calc.py rename to from_3b1b/old/alt_calc.py index e38e7ebe0b..c4d6ccc5e6 100644 --- a/old_projects/alt_calc.py +++ b/from_3b1b/old/alt_calc.py @@ -746,7 +746,7 @@ def get_getting_stuck_image(self): def get_aha_image(self): creature = self.pi_creature.copy() creature.change_mode("hooray") - from old_projects.eoc.chapter3 import NudgeSideLengthOfCube + from from_3b1b.old.eoc.chapter3 import NudgeSideLengthOfCube scene = NudgeSideLengthOfCube( end_at_animation_number=7, skip_animations=True diff --git a/old_projects/basel/basel.py b/from_3b1b/old/basel/basel.py similarity index 100% rename from old_projects/basel/basel.py rename to from_3b1b/old/basel/basel.py diff --git a/old_projects/basel/basel2.py b/from_3b1b/old/basel/basel2.py similarity index 100% rename from old_projects/basel/basel2.py rename to from_3b1b/old/basel/basel2.py diff --git a/old_projects/bell.py b/from_3b1b/old/bell.py similarity index 100% rename from old_projects/bell.py rename to from_3b1b/old/bell.py diff --git a/old_projects/borsuk.py b/from_3b1b/old/borsuk.py similarity index 100% rename from old_projects/borsuk.py rename to from_3b1b/old/borsuk.py diff --git a/old_projects/borsuk_addition.py b/from_3b1b/old/borsuk_addition.py similarity index 99% rename from old_projects/borsuk_addition.py rename to from_3b1b/old/borsuk_addition.py index 598aef5b4f..0c59f3caec 100644 --- a/old_projects/borsuk_addition.py +++ b/from_3b1b/old/borsuk_addition.py @@ -1,7 +1,7 @@ from manimlib.imports import * -from old_projects.lost_lecture import GeometryProofLand -from old_projects.quaternions import SpecialThreeDScene -from old_projects.uncertainty import Flash +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): @@ -807,7 +807,7 @@ def setup(self): x_max=2.5, y_min=-2.5, y_max=2.5, - number_line_config={'unit_size': 1.5} + axis_config={'unit_size': 1.5} ) for axis in axes: numbers = list(range(-2, 3)) diff --git a/old_projects/brachistochrone/curves.py b/from_3b1b/old/brachistochrone/curves.py similarity index 100% rename from old_projects/brachistochrone/curves.py rename to from_3b1b/old/brachistochrone/curves.py diff --git a/old_projects/brachistochrone/cycloid.py b/from_3b1b/old/brachistochrone/cycloid.py similarity index 99% rename from old_projects/brachistochrone/cycloid.py rename to from_3b1b/old/brachistochrone/cycloid.py index 1c4c950bc1..8013dc56a5 100644 --- a/old_projects/brachistochrone/cycloid.py +++ b/from_3b1b/old/brachistochrone/cycloid.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.brachistochrone.curves import * +from from_3b1b.old.brachistochrone.curves import * class RollAlongVector(Animation): CONFIG = { diff --git a/old_projects/brachistochrone/drawing_images.py b/from_3b1b/old/brachistochrone/drawing_images.py similarity index 100% rename from old_projects/brachistochrone/drawing_images.py rename to from_3b1b/old/brachistochrone/drawing_images.py diff --git a/old_projects/brachistochrone/graveyard.py b/from_3b1b/old/brachistochrone/graveyard.py similarity index 99% rename from old_projects/brachistochrone/graveyard.py rename to from_3b1b/old/brachistochrone/graveyard.py index 0e1c00c214..aa2a21557e 100644 --- a/old_projects/brachistochrone/graveyard.py +++ b/from_3b1b/old/brachistochrone/graveyard.py @@ -3,7 +3,7 @@ from manimlib.imports import * -from old_projects.brachistochrone.curves import Cycloid +from from_3b1b.old.brachistochrone.curves import Cycloid class MultilayeredGlass(PhotonScene, ZoomedScene): CONFIG = { diff --git a/old_projects/brachistochrone/light.py b/from_3b1b/old/brachistochrone/light.py similarity index 99% rename from old_projects/brachistochrone/light.py rename to from_3b1b/old/brachistochrone/light.py index b1037e12a6..2543611ee4 100644 --- a/old_projects/brachistochrone/light.py +++ b/from_3b1b/old/brachistochrone/light.py @@ -3,7 +3,7 @@ from manimlib.imports import * -from old_projects.brachistochrone.curves import \ +from from_3b1b.old.brachistochrone.curves import \ Cycloid, PathSlidingScene, RANDY_SCALE_FACTOR, TryManyPaths diff --git a/old_projects/brachistochrone/misc.py b/from_3b1b/old/brachistochrone/misc.py similarity index 99% rename from old_projects/brachistochrone/misc.py rename to from_3b1b/old/brachistochrone/misc.py index 77ceaf8b5b..a887d34895 100644 --- a/old_projects/brachistochrone/misc.py +++ b/from_3b1b/old/brachistochrone/misc.py @@ -2,7 +2,7 @@ import itertools as it from manimlib.imports import * -from old_projects.brachistochrone.curves import Cycloid +from from_3b1b.old.brachistochrone.curves import Cycloid class PhysicalIntuition(Scene): def construct(self): diff --git a/old_projects/brachistochrone/multilayered.py b/from_3b1b/old/brachistochrone/multilayered.py similarity index 99% rename from old_projects/brachistochrone/multilayered.py rename to from_3b1b/old/brachistochrone/multilayered.py index 2d81cc1e6d..ab86c586e0 100644 --- a/old_projects/brachistochrone/multilayered.py +++ b/from_3b1b/old/brachistochrone/multilayered.py @@ -2,8 +2,8 @@ import itertools as it from manimlib.imports import * -from old_projects.brachistochrone.light import PhotonScene -from old_projects.brachistochrone.curves import * +from from_3b1b.old.brachistochrone.light import PhotonScene +from from_3b1b.old.brachistochrone.curves import * class MultilayeredScene(Scene): diff --git a/old_projects/brachistochrone/wordplay.py b/from_3b1b/old/brachistochrone/wordplay.py similarity index 99% rename from old_projects/brachistochrone/wordplay.py rename to from_3b1b/old/brachistochrone/wordplay.py index 7ac6c5f2e9..281885f25f 100644 --- a/old_projects/brachistochrone/wordplay.py +++ b/from_3b1b/old/brachistochrone/wordplay.py @@ -3,7 +3,7 @@ import os from manimlib.imports import * -from old_projects.brachistochrone.drawing_images import sort_by_color +from from_3b1b.old.brachistochrone.drawing_images import sort_by_color class Intro(Scene): def construct(self): diff --git a/old_projects/cba.py b/from_3b1b/old/cba.py similarity index 100% rename from old_projects/cba.py rename to from_3b1b/old/cba.py diff --git a/old_projects/clacks/all_s2_scenes.py b/from_3b1b/old/clacks/all_s2_scenes.py similarity index 85% rename from old_projects/clacks/all_s2_scenes.py rename to from_3b1b/old/clacks/all_s2_scenes.py index 55635afeb8..efca6a1f53 100644 --- a/old_projects/clacks/all_s2_scenes.py +++ b/from_3b1b/old/clacks/all_s2_scenes.py @@ -1,10 +1,10 @@ -from old_projects.clacks import question -from old_projects.clacks.solution2 import block_collision_scenes -from old_projects.clacks.solution2 import mirror_scenes -from old_projects.clacks.solution2 import pi_creature_scenes -from old_projects.clacks.solution2 import position_phase_space -from old_projects.clacks.solution2 import simple_scenes -from old_projects.clacks.solution2 import wordy_scenes +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 = [ diff --git a/old_projects/clacks/name_bump.py b/from_3b1b/old/clacks/name_bump.py similarity index 97% rename from old_projects/clacks/name_bump.py rename to from_3b1b/old/clacks/name_bump.py index cfc52e8c25..5e817882d8 100644 --- a/old_projects/clacks/name_bump.py +++ b/from_3b1b/old/clacks/name_bump.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from manimlib.imports import * -from old_projects.clacks.question import BlocksAndWallExample +from from_3b1b.old.clacks.question import BlocksAndWallExample class NameBump(BlocksAndWallExample): diff --git a/old_projects/clacks/question.py b/from_3b1b/old/clacks/question.py similarity index 99% rename from old_projects/clacks/question.py rename to from_3b1b/old/clacks/question.py index b022c2dd61..a3b61cb5cc 100644 --- a/old_projects/clacks/question.py +++ b/from_3b1b/old/clacks/question.py @@ -325,8 +325,8 @@ def add_counter(self): counter_mob = Integer(self.n_clacks) counter_mob.next_to( counter_label[-1], RIGHT, - aligned_edge=DOWN, ) + counter_mob.align_to(counter_label[-1][-1], DOWN) counter_group = VGroup( counter_label, counter_mob, @@ -747,7 +747,7 @@ class BlocksAndWallExampleMass1e2(BlocksAndWallExample): "velocity": -0.6, } }, - "wait_time": 25, + "wait_time": 35, } @@ -888,7 +888,7 @@ def setup_axes(self): y_min=0, x_max=9, y_max=5, - number_line_config={ + axis_config={ "tick_frequency": 100, "numbers_with_elongated_ticks": [], } diff --git a/old_projects/clacks/solution1.py b/from_3b1b/old/clacks/solution1.py similarity index 99% rename from old_projects/clacks/solution1.py rename to from_3b1b/old/clacks/solution1.py index 92ec39374d..8751802ca0 100644 --- a/old_projects/clacks/solution1.py +++ b/from_3b1b/old/clacks/solution1.py @@ -1,6 +1,6 @@ from manimlib.imports import * -from old_projects.clacks.question import * -from old_projects.div_curl import ShowTwoPopulations +from from_3b1b.old.clacks.question import * +from from_3b1b.old.div_curl import ShowTwoPopulations OUTPUT_DIRECTORY = "clacks/solution1" @@ -633,7 +633,7 @@ class IntroduceVelocityPhaseSpace(AskAboutFindingNewVelocities): "x_min": -3.5, "x_max": 4, }, - "number_line_config": { + "axis_config": { "unit_size": 0.7, }, }, @@ -1204,6 +1204,8 @@ class CircleDiagramFromSlidingBlocks(Scene): "fill_color": GREEN, "fill_opacity": 0.3, }, + "show_dot": True, + "show_vector": False, } def construct(self): @@ -1211,6 +1213,9 @@ def construct(self): 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] @@ -1229,7 +1234,17 @@ def show_circle_lines(self, times, slope): dot = Dot(color=RED, radius=0.06) dot.move_to(lines[0].get_start()) - self.add(end_zone, axes, circle, dot) + 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): @@ -1238,7 +1253,7 @@ def show_circle_lines(self, times, slope): self.wait(time - last_time) last_time = time dot.move_to(line.get_end()) - self.add(line, dot) + self.add(line, dot, vector) self.wait() def get_circle(self): @@ -2597,7 +2612,7 @@ def construct(self): def draw_unit_circle(self): unit_size = 2.5 axes = Axes( - number_line_config={"unit_size": unit_size}, + axis_config={"unit_size": unit_size}, x_min=-2.5, x_max=2.5, y_min=-1.5, y_max=1.5, ) diff --git a/old_projects/clacks/solution2/block_collision_scenes.py b/from_3b1b/old/clacks/solution2/block_collision_scenes.py similarity index 97% rename from old_projects/clacks/solution2/block_collision_scenes.py rename to from_3b1b/old/clacks/solution2/block_collision_scenes.py index 077b5541d5..824b794333 100644 --- a/old_projects/clacks/solution2/block_collision_scenes.py +++ b/from_3b1b/old/clacks/solution2/block_collision_scenes.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.clacks.question import BlocksAndWallExample +from from_3b1b.old.clacks.question import BlocksAndWallExample class PreviousTwoVideos(BlocksAndWallExample): diff --git a/old_projects/clacks/solution2/mirror_scenes.py b/from_3b1b/old/clacks/solution2/mirror_scenes.py similarity index 100% rename from old_projects/clacks/solution2/mirror_scenes.py rename to from_3b1b/old/clacks/solution2/mirror_scenes.py diff --git a/old_projects/clacks/solution2/pi_creature_scenes.py b/from_3b1b/old/clacks/solution2/pi_creature_scenes.py similarity index 100% rename from old_projects/clacks/solution2/pi_creature_scenes.py rename to from_3b1b/old/clacks/solution2/pi_creature_scenes.py diff --git a/old_projects/clacks/solution2/position_phase_space.py b/from_3b1b/old/clacks/solution2/position_phase_space.py similarity index 99% rename from old_projects/clacks/solution2/position_phase_space.py rename to from_3b1b/old/clacks/solution2/position_phase_space.py index 75877283df..46f82921ff 100644 --- a/old_projects/clacks/solution2/position_phase_space.py +++ b/from_3b1b/old/clacks/solution2/position_phase_space.py @@ -1,7 +1,7 @@ from manimlib.imports import * -from old_projects.clacks.question import Block -from old_projects.clacks.question import Wall -from old_projects.clacks.question import ClackFlashes +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): diff --git a/old_projects/clacks/solution2/simple_scenes.py b/from_3b1b/old/clacks/solution2/simple_scenes.py similarity index 99% rename from old_projects/clacks/solution2/simple_scenes.py rename to from_3b1b/old/clacks/solution2/simple_scenes.py index d2921bce7b..988ac98ddf 100644 --- a/old_projects/clacks/solution2/simple_scenes.py +++ b/from_3b1b/old/clacks/solution2/simple_scenes.py @@ -1,7 +1,7 @@ from manimlib.imports import * -from old_projects.lost_lecture import ShowWord -from old_projects.clacks.solution2.mirror_scenes import ReflectWorldThroughMirrorNew -from old_projects.clacks.question import Thumbnail +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): diff --git a/old_projects/clacks/solution2/wordy_scenes.py b/from_3b1b/old/clacks/solution2/wordy_scenes.py similarity index 99% rename from old_projects/clacks/solution2/wordy_scenes.py rename to from_3b1b/old/clacks/solution2/wordy_scenes.py index a665315eb5..39f880d0f0 100644 --- a/old_projects/clacks/solution2/wordy_scenes.py +++ b/from_3b1b/old/clacks/solution2/wordy_scenes.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.clacks.solution2.position_phase_space import ShowMomentumConservation +from from_3b1b.old.clacks.solution2.position_phase_space import ShowMomentumConservation class ConnectionToOptics(Scene): diff --git a/old_projects/complex_multiplication_article.py b/from_3b1b/old/complex_multiplication_article.py similarity index 100% rename from old_projects/complex_multiplication_article.py rename to from_3b1b/old/complex_multiplication_article.py diff --git a/old_projects/counting_in_binary.py b/from_3b1b/old/counting_in_binary.py similarity index 100% rename from old_projects/counting_in_binary.py rename to from_3b1b/old/counting_in_binary.py diff --git a/old_projects/crypto.py b/from_3b1b/old/crypto.py similarity index 100% rename from old_projects/crypto.py rename to from_3b1b/old/crypto.py diff --git a/old_projects/dandelin.py b/from_3b1b/old/dandelin.py similarity index 99% rename from old_projects/dandelin.py rename to from_3b1b/old/dandelin.py index b692072db4..d89482c17c 100644 --- a/old_projects/dandelin.py +++ b/from_3b1b/old/dandelin.py @@ -1,7 +1,7 @@ from manimlib.imports import * -from old_projects.lost_lecture import Orbiting -from old_projects.lost_lecture import ShowWord +from from_3b1b.old.lost_lecture import Orbiting +from from_3b1b.old.lost_lecture import ShowWord class LogoGeneration(LogoGenerationTemplate): diff --git a/old_projects/div_curl.py b/from_3b1b/old/div_curl.py similarity index 99% rename from old_projects/div_curl.py rename to from_3b1b/old/div_curl.py index 17c2399403..90731ce2a9 100644 --- a/old_projects/div_curl.py +++ b/from_3b1b/old/div_curl.py @@ -2925,7 +2925,7 @@ def add_axes(self): y_min=0, y_max=55, y_axis_config={"unit_size": 0.09}, - number_line_config={ + axis_config={ "tick_frequency": 10, }, ) @@ -4217,7 +4217,7 @@ def just_you_and_me(self): spiral = Line(0.5 * RIGHT, 0.5 * RIGHT + 70 * UP) spiral.insert_n_curves(1000) - from old_projects.zeta import zeta + from from_3b1b.old.zeta import zeta spiral.apply_complex_function(zeta) step = 0.1 spiral = VGroup(*[ diff --git a/old_projects/domino_play.py b/from_3b1b/old/domino_play.py similarity index 100% rename from old_projects/domino_play.py rename to from_3b1b/old/domino_play.py diff --git a/old_projects/dominos/data01.txt b/from_3b1b/old/dominos/data01.txt similarity index 100% rename from old_projects/dominos/data01.txt rename to from_3b1b/old/dominos/data01.txt diff --git a/old_projects/dominos/data02.txt b/from_3b1b/old/dominos/data02.txt similarity index 100% rename from old_projects/dominos/data02.txt rename to from_3b1b/old/dominos/data02.txt diff --git a/old_projects/dominos/data03.txt b/from_3b1b/old/dominos/data03.txt similarity index 100% rename from old_projects/dominos/data03.txt rename to from_3b1b/old/dominos/data03.txt diff --git a/old_projects/dominos/data04.txt b/from_3b1b/old/dominos/data04.txt similarity index 100% rename from old_projects/dominos/data04.txt rename to from_3b1b/old/dominos/data04.txt diff --git a/old_projects/dominos/data05.txt b/from_3b1b/old/dominos/data05.txt similarity index 100% rename from old_projects/dominos/data05.txt rename to from_3b1b/old/dominos/data05.txt diff --git a/old_projects/dominos/data06.txt b/from_3b1b/old/dominos/data06.txt similarity index 100% rename from old_projects/dominos/data06.txt rename to from_3b1b/old/dominos/data06.txt diff --git a/old_projects/dominos/data07.txt b/from_3b1b/old/dominos/data07.txt similarity index 100% rename from old_projects/dominos/data07.txt rename to from_3b1b/old/dominos/data07.txt diff --git a/old_projects/dominos/data08.txt b/from_3b1b/old/dominos/data08.txt similarity index 100% rename from old_projects/dominos/data08.txt rename to from_3b1b/old/dominos/data08.txt diff --git a/old_projects/dominos/data09.txt b/from_3b1b/old/dominos/data09.txt similarity index 100% rename from old_projects/dominos/data09.txt rename to from_3b1b/old/dominos/data09.txt diff --git a/old_projects/dominos/data10.txt b/from_3b1b/old/dominos/data10.txt similarity index 100% rename from old_projects/dominos/data10.txt rename to from_3b1b/old/dominos/data10.txt diff --git a/old_projects/dominos/data11.txt b/from_3b1b/old/dominos/data11.txt similarity index 100% rename from old_projects/dominos/data11.txt rename to from_3b1b/old/dominos/data11.txt diff --git a/old_projects/dominos/data12.txt b/from_3b1b/old/dominos/data12.txt similarity index 100% rename from old_projects/dominos/data12.txt rename to from_3b1b/old/dominos/data12.txt diff --git a/old_projects/dominos/data13.txt b/from_3b1b/old/dominos/data13.txt similarity index 100% rename from old_projects/dominos/data13.txt rename to from_3b1b/old/dominos/data13.txt diff --git a/old_projects/dominos/data14.txt b/from_3b1b/old/dominos/data14.txt similarity index 100% rename from old_projects/dominos/data14.txt rename to from_3b1b/old/dominos/data14.txt diff --git a/old_projects/dominos/data15.txt b/from_3b1b/old/dominos/data15.txt similarity index 100% rename from old_projects/dominos/data15.txt rename to from_3b1b/old/dominos/data15.txt diff --git a/old_projects/dominos/data16.txt b/from_3b1b/old/dominos/data16.txt similarity index 100% rename from old_projects/dominos/data16.txt rename to from_3b1b/old/dominos/data16.txt diff --git a/old_projects/dominos/data17.txt b/from_3b1b/old/dominos/data17.txt similarity index 100% rename from old_projects/dominos/data17.txt rename to from_3b1b/old/dominos/data17.txt diff --git a/old_projects/dominos/data18.txt b/from_3b1b/old/dominos/data18.txt similarity index 100% rename from old_projects/dominos/data18.txt rename to from_3b1b/old/dominos/data18.txt diff --git a/old_projects/dominos/data19.txt b/from_3b1b/old/dominos/data19.txt similarity index 100% rename from old_projects/dominos/data19.txt rename to from_3b1b/old/dominos/data19.txt diff --git a/old_projects/efvgt.py b/from_3b1b/old/efvgt.py similarity index 99% rename from old_projects/efvgt.py rename to from_3b1b/old/efvgt.py index f1b8d53c78..56880d5374 100644 --- a/old_projects/efvgt.py +++ b/from_3b1b/old/efvgt.py @@ -90,13 +90,14 @@ def get_confetti_animations(num_confetti_squares): class Anniversary(TeacherStudentsScene): CONFIG = { "num_confetti_squares" : 50, + "message": "2 year Anniversary!", } def construct(self): self.celebrate() self.complain() def celebrate(self): - title = TextMobject("2 year Anniversary!") + title = TextMobject(self.message) title.scale(1.5) title.to_edge(UP) @@ -111,6 +112,7 @@ def celebrate(self): 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( diff --git a/old_projects/eoc/chapter1.py b/from_3b1b/old/eoc/chapter1.py similarity index 99% rename from old_projects/eoc/chapter1.py rename to from_3b1b/old/eoc/chapter1.py index 2f8b18b526..a51dc690e7 100644 --- a/old_projects/eoc/chapter1.py +++ b/from_3b1b/old/eoc/chapter1.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eoc.chapter2 import Car, MoveCar +from from_3b1b.old.eoc.chapter2 import Car, MoveCar class CircleScene(PiCreatureScene): CONFIG = { diff --git a/old_projects/eoc/chapter10.py b/from_3b1b/old/eoc/chapter10.py similarity index 100% rename from old_projects/eoc/chapter10.py rename to from_3b1b/old/eoc/chapter10.py diff --git a/old_projects/eoc/chapter2.py b/from_3b1b/old/eoc/chapter2.py similarity index 100% rename from old_projects/eoc/chapter2.py rename to from_3b1b/old/eoc/chapter2.py diff --git a/old_projects/eoc/chapter3.py b/from_3b1b/old/eoc/chapter3.py similarity index 99% rename from old_projects/eoc/chapter3.py rename to from_3b1b/old/eoc/chapter3.py index 93ac1ee578..736bf0dd82 100644 --- a/old_projects/eoc/chapter3.py +++ b/from_3b1b/old/eoc/chapter3.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eoc.chapter2 import DISTANCE_COLOR, TIME_COLOR, \ +from from_3b1b.old.eoc.chapter2 import DISTANCE_COLOR, TIME_COLOR, \ VELOCITY_COLOR, Car, MoveCar OUTPUT_COLOR = DISTANCE_COLOR diff --git a/old_projects/eoc/chapter4.py b/from_3b1b/old/eoc/chapter4.py similarity index 100% rename from old_projects/eoc/chapter4.py rename to from_3b1b/old/eoc/chapter4.py diff --git a/old_projects/eoc/chapter5.py b/from_3b1b/old/eoc/chapter5.py similarity index 99% rename from old_projects/eoc/chapter5.py rename to from_3b1b/old/eoc/chapter5.py index e08585f2da..600b93deb7 100644 --- a/old_projects/eoc/chapter5.py +++ b/from_3b1b/old/eoc/chapter5.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eoc.chapter4 import ThreeLinesChainRule +from from_3b1b.old.eoc.chapter4 import ThreeLinesChainRule class ExpFootnoteOpeningQuote(OpeningQuote): CONFIG = { diff --git a/old_projects/eoc/chapter6.py b/from_3b1b/old/eoc/chapter6.py similarity index 100% rename from old_projects/eoc/chapter6.py rename to from_3b1b/old/eoc/chapter6.py diff --git a/old_projects/eoc/chapter7.py b/from_3b1b/old/eoc/chapter7.py similarity index 100% rename from old_projects/eoc/chapter7.py rename to from_3b1b/old/eoc/chapter7.py diff --git a/old_projects/eoc/chapter8.py b/from_3b1b/old/eoc/chapter8.py similarity index 99% rename from old_projects/eoc/chapter8.py rename to from_3b1b/old/eoc/chapter8.py index 077ec25e74..b08fce4900 100644 --- a/old_projects/eoc/chapter8.py +++ b/from_3b1b/old/eoc/chapter8.py @@ -1,7 +1,7 @@ import scipy from manimlib.imports import * -from old_projects.eoc.chapter1 import Thumbnail as Chapter1Thumbnail -from old_projects.eoc.chapter2 import Car, MoveCar, ShowSpeedometer, \ +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 diff --git a/old_projects/eoc/chapter9.py b/from_3b1b/old/eoc/chapter9.py similarity index 100% rename from old_projects/eoc/chapter9.py rename to from_3b1b/old/eoc/chapter9.py diff --git a/old_projects/eoc/footnote.py b/from_3b1b/old/eoc/footnote.py similarity index 99% rename from old_projects/eoc/footnote.py rename to from_3b1b/old/eoc/footnote.py index 8e27970e21..444324bd52 100644 --- a/old_projects/eoc/footnote.py +++ b/from_3b1b/old/eoc/footnote.py @@ -2,8 +2,8 @@ import math from manimlib.imports import * -from old_projects.eoc.chapter1 import Car, MoveCar -from old_projects.eoc.chapter10 import derivative +from from_3b1b.old.eoc.chapter1 import Car, MoveCar +from from_3b1b.old.eoc.chapter10 import derivative #revert_to_original_skipping_status diff --git a/old_projects/eoc/old_chapter1.py b/from_3b1b/old/eoc/old_chapter1.py similarity index 100% rename from old_projects/eoc/old_chapter1.py rename to from_3b1b/old/eoc/old_chapter1.py diff --git a/old_projects/eola/chapter0.py b/from_3b1b/old/eola/chapter0.py similarity index 100% rename from old_projects/eola/chapter0.py rename to from_3b1b/old/eola/chapter0.py diff --git a/old_projects/eola/chapter1.py b/from_3b1b/old/eola/chapter1.py similarity index 99% rename from old_projects/eola/chapter1.py rename to from_3b1b/old/eola/chapter1.py index 64787e0d96..8f0b8efaaf 100644 --- a/old_projects/eola/chapter1.py +++ b/from_3b1b/old/eola/chapter1.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter0 import UpcomingSeriesOfVidoes +from from_3b1b.old.eola.chapter0 import UpcomingSeriesOfVidoes import random diff --git a/old_projects/eola/chapter10.py b/from_3b1b/old/eola/chapter10.py similarity index 99% rename from old_projects/eola/chapter10.py rename to from_3b1b/old/eola/chapter10.py index 4fc1447b49..8b2c71f7f2 100644 --- a/old_projects/eola/chapter10.py +++ b/from_3b1b/old/eola/chapter10.py @@ -1,8 +1,8 @@ from manimlib.imports import * -from old_projects.eola.chapter1 import plane_wave_homotopy -from old_projects.eola.chapter3 import ColumnsToBasisVectors -from old_projects.eola.chapter5 import get_det_text -from old_projects.eola.chapter9 import get_small_bubble +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): diff --git a/old_projects/eola/chapter11.py b/from_3b1b/old/eola/chapter11.py similarity index 99% rename from old_projects/eola/chapter11.py rename to from_3b1b/old/eola/chapter11.py index a62a61b175..7d9931b45a 100644 --- a/old_projects/eola/chapter11.py +++ b/from_3b1b/old/eola/chapter11.py @@ -1,9 +1,9 @@ from manimlib.imports import * -from old_projects.eola.chapter1 import plane_wave_homotopy -from old_projects.eola.chapter3 import ColumnsToBasisVectors -from old_projects.eola.chapter5 import NameDeterminant, Blob -from old_projects.eola.chapter9 import get_small_bubble -from old_projects.eola.chapter10 import ExampleTranformationScene +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 = { diff --git a/old_projects/eola/chapter2.py b/from_3b1b/old/eola/chapter2.py similarity index 99% rename from old_projects/eola/chapter2.py rename to from_3b1b/old/eola/chapter2.py index 55689f5590..4fadb2d544 100644 --- a/old_projects/eola/chapter2.py +++ b/from_3b1b/old/eola/chapter2.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter1 import plane_wave_homotopy +from from_3b1b.old.eola.chapter1 import plane_wave_homotopy class OpeningQuote(Scene): def construct(self): diff --git a/old_projects/eola/chapter3.py b/from_3b1b/old/eola/chapter3.py similarity index 100% rename from old_projects/eola/chapter3.py rename to from_3b1b/old/eola/chapter3.py diff --git a/old_projects/eola/chapter4.py b/from_3b1b/old/eola/chapter4.py similarity index 99% rename from old_projects/eola/chapter4.py rename to from_3b1b/old/eola/chapter4.py index 8bda4621a2..d2058586fb 100644 --- a/old_projects/eola/chapter4.py +++ b/from_3b1b/old/eola/chapter4.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter3 import MatrixVectorMultiplicationAbstract +from from_3b1b.old.eola.chapter3 import MatrixVectorMultiplicationAbstract class OpeningQuote(Scene): diff --git a/old_projects/eola/chapter5.py b/from_3b1b/old/eola/chapter5.py similarity index 99% rename from old_projects/eola/chapter5.py rename to from_3b1b/old/eola/chapter5.py index 2d21f24f39..60e5b6078d 100644 --- a/old_projects/eola/chapter5.py +++ b/from_3b1b/old/eola/chapter5.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter3 import MatrixVectorMultiplicationAbstract +from from_3b1b.old.eola.chapter3 import MatrixVectorMultiplicationAbstract class Blob(Circle): diff --git a/old_projects/eola/chapter6.py b/from_3b1b/old/eola/chapter6.py similarity index 100% rename from old_projects/eola/chapter6.py rename to from_3b1b/old/eola/chapter6.py diff --git a/old_projects/eola/chapter7.py b/from_3b1b/old/eola/chapter7.py similarity index 99% rename from old_projects/eola/chapter7.py rename to from_3b1b/old/eola/chapter7.py index a851eeb9a2..2da6c5df9b 100644 --- a/old_projects/eola/chapter7.py +++ b/from_3b1b/old/eola/chapter7.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.footnote2 import TwoDTo1DTransformWithDots +from from_3b1b.old.eola.footnote2 import TwoDTo1DTransformWithDots V_COLOR = YELLOW diff --git a/old_projects/eola/chapter8.py b/from_3b1b/old/eola/chapter8.py similarity index 99% rename from old_projects/eola/chapter8.py rename to from_3b1b/old/eola/chapter8.py index 9e0d38d7f7..44aaef4a76 100644 --- a/old_projects/eola/chapter8.py +++ b/from_3b1b/old/eola/chapter8.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter5 import get_det_text, RightHandRule +from from_3b1b.old.eola.chapter5 import get_det_text, RightHandRule U_COLOR = ORANGE diff --git a/old_projects/eola/chapter8p2.py b/from_3b1b/old/eola/chapter8p2.py similarity index 99% rename from old_projects/eola/chapter8p2.py rename to from_3b1b/old/eola/chapter8p2.py index f72b261179..e0a5c80bdf 100644 --- a/old_projects/eola/chapter8p2.py +++ b/from_3b1b/old/eola/chapter8p2.py @@ -1,6 +1,6 @@ from manimlib.imports import * -from old_projects.eola.chapter5 import get_det_text -from old_projects.eola.chapter8 import * +from from_3b1b.old.eola.chapter5 import get_det_text +from from_3b1b.old.eola.chapter8 import * class OpeningQuote(Scene): diff --git a/old_projects/eola/chapter9.py b/from_3b1b/old/eola/chapter9.py similarity index 99% rename from old_projects/eola/chapter9.py rename to from_3b1b/old/eola/chapter9.py index 9500460865..04735bb05a 100644 --- a/old_projects/eola/chapter9.py +++ b/from_3b1b/old/eola/chapter9.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter1 import plane_wave_homotopy +from from_3b1b.old.eola.chapter1 import plane_wave_homotopy V_COLOR = YELLOW diff --git a/old_projects/eola/footnote.py b/from_3b1b/old/eola/footnote.py similarity index 100% rename from old_projects/eola/footnote.py rename to from_3b1b/old/eola/footnote.py diff --git a/old_projects/eola/footnote2.py b/from_3b1b/old/eola/footnote2.py similarity index 100% rename from old_projects/eola/footnote2.py rename to from_3b1b/old/eola/footnote2.py diff --git a/old_projects/eola/thumbnails.py b/from_3b1b/old/eola/thumbnails.py similarity index 98% rename from old_projects/eola/thumbnails.py rename to from_3b1b/old/eola/thumbnails.py index 0c425d1f7a..1510bd6bc5 100644 --- a/old_projects/eola/thumbnails.py +++ b/from_3b1b/old/eola/thumbnails.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.eola.chapter9 import Jennifer, You +from from_3b1b.old.eola.chapter9 import Jennifer, You class Chapter0(LinearTransformationScene): CONFIG = { diff --git a/old_projects/eulers_characteristic_formula.py b/from_3b1b/old/eulers_characteristic_formula.py similarity index 100% rename from old_projects/eulers_characteristic_formula.py rename to from_3b1b/old/eulers_characteristic_formula.py diff --git a/old_projects/fc1.py b/from_3b1b/old/fc1.py similarity index 99% rename from old_projects/fc1.py rename to from_3b1b/old/fc1.py index f46e7adad3..8943e25f1c 100644 --- a/old_projects/fc1.py +++ b/from_3b1b/old/fc1.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.efvgt import get_confetti_animations +from from_3b1b.old.efvgt import get_confetti_animations class CrossingOneMillion(TeacherStudentsScene): diff --git a/old_projects/for_flammy.py b/from_3b1b/old/for_flammy.py similarity index 99% rename from old_projects/for_flammy.py rename to from_3b1b/old/for_flammy.py index 12642942d7..fbd7045fb4 100644 --- a/old_projects/for_flammy.py +++ b/from_3b1b/old/for_flammy.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.sphere_area import * +from from_3b1b.old.sphere_area import * class MadAtMathologer(PiCreatureScene): diff --git a/old_projects/fourier.py b/from_3b1b/old/fourier.py similarity index 99% rename from old_projects/fourier.py rename to from_3b1b/old/fourier.py index aae267d251..72427d0896 100644 --- a/old_projects/fourier.py +++ b/from_3b1b/old/fourier.py @@ -190,7 +190,7 @@ def measure_air_pressure(self): axes = Axes( y_min = -2, y_max = 2, x_min = 0, x_max = 10, - number_line_config = {"include_tip" : False}, + axis_config = {"include_tip" : False}, ) axes.stretch_to_fit_height(2) axes.to_corner(UP+LEFT) @@ -807,7 +807,7 @@ class FourierMachineScene(Scene): "y_unit_size" : 1, }, "frequency_axes_config" : { - "number_line_config" : { + "axis_config" : { "color" : TEAL, }, "x_min" : 0, @@ -3906,7 +3906,7 @@ def add_graph(self): x_max = 140, y_min = -2, y_max = 2, - number_line_config = { + axis_config = { "include_tip" : False, }, ) diff --git a/old_projects/fractal_charm.py b/from_3b1b/old/fractal_charm.py similarity index 100% rename from old_projects/fractal_charm.py rename to from_3b1b/old/fractal_charm.py diff --git a/old_projects/fractal_dimension.py b/from_3b1b/old/fractal_dimension.py similarity index 100% rename from old_projects/fractal_dimension.py rename to from_3b1b/old/fractal_dimension.py diff --git a/old_projects/generate_logo.py b/from_3b1b/old/generate_logo.py similarity index 100% rename from old_projects/generate_logo.py rename to from_3b1b/old/generate_logo.py diff --git a/old_projects/gradient.py b/from_3b1b/old/gradient.py similarity index 100% rename from old_projects/gradient.py rename to from_3b1b/old/gradient.py diff --git a/old_projects/hanoi.py b/from_3b1b/old/hanoi.py similarity index 100% rename from old_projects/hanoi.py rename to from_3b1b/old/hanoi.py diff --git a/old_projects/highD.py b/from_3b1b/old/highD.py similarity index 100% rename from old_projects/highD.py rename to from_3b1b/old/highD.py diff --git a/old_projects/hilbert/fractal_porn.py b/from_3b1b/old/hilbert/fractal_porn.py similarity index 99% rename from old_projects/hilbert/fractal_porn.py rename to from_3b1b/old/hilbert/fractal_porn.py index 64a3ca1220..fa9335b0a4 100644 --- a/old_projects/hilbert/fractal_porn.py +++ b/from_3b1b/old/hilbert/fractal_porn.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.hilbert.curves import * +from from_3b1b.old.hilbert.curves import * class Intro(TransformOverIncreasingOrders): @staticmethod diff --git a/old_projects/hilbert/section1.py b/from_3b1b/old/hilbert/section1.py similarity index 100% rename from old_projects/hilbert/section1.py rename to from_3b1b/old/hilbert/section1.py diff --git a/old_projects/hilbert/section2.py b/from_3b1b/old/hilbert/section2.py similarity index 100% rename from old_projects/hilbert/section2.py rename to from_3b1b/old/hilbert/section2.py diff --git a/old_projects/hilbert/section3.py b/from_3b1b/old/hilbert/section3.py similarity index 100% rename from old_projects/hilbert/section3.py rename to from_3b1b/old/hilbert/section3.py diff --git a/from_3b1b/old/hyperdarts.py b/from_3b1b/old/hyperdarts.py new file mode 100644 index 0000000000..23e76e7f00 --- /dev/null +++ b/from_3b1b/old/hyperdarts.py @@ -0,0 +1,2826 @@ +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/old_projects/inventing_math.py b/from_3b1b/old/inventing_math.py similarity index 100% rename from old_projects/inventing_math.py rename to from_3b1b/old/inventing_math.py diff --git a/old_projects/inventing_math_images.py b/from_3b1b/old/inventing_math_images.py similarity index 100% rename from old_projects/inventing_math_images.py rename to from_3b1b/old/inventing_math_images.py diff --git a/old_projects/leibniz.py b/from_3b1b/old/leibniz.py similarity index 100% rename from old_projects/leibniz.py rename to from_3b1b/old/leibniz.py diff --git a/old_projects/lost_lecture.py b/from_3b1b/old/lost_lecture.py similarity index 99% rename from old_projects/lost_lecture.py rename to from_3b1b/old/lost_lecture.py index f3ff992bb1..caaef69401 100644 --- a/old_projects/lost_lecture.py +++ b/from_3b1b/old/lost_lecture.py @@ -1,8 +1,8 @@ from manimlib.imports import * -from old_projects.div_curl import VectorField -from old_projects.div_curl import get_force_field_func +from from_3b1b.old.div_curl import VectorField +from from_3b1b.old.div_curl import get_force_field_func COBALT = "#0047AB" diff --git a/old_projects/matrix_as_transform_2d.py b/from_3b1b/old/matrix_as_transform_2d.py similarity index 100% rename from old_projects/matrix_as_transform_2d.py rename to from_3b1b/old/matrix_as_transform_2d.py diff --git a/old_projects/moser_intro.py b/from_3b1b/old/moser_intro.py similarity index 100% rename from old_projects/moser_intro.py rename to from_3b1b/old/moser_intro.py diff --git a/old_projects/moser_main.py b/from_3b1b/old/moser_main.py similarity index 100% rename from old_projects/moser_main.py rename to from_3b1b/old/moser_main.py diff --git a/old_projects/mug.py b/from_3b1b/old/mug.py similarity index 99% rename from old_projects/mug.py rename to from_3b1b/old/mug.py index a749ee916b..3e2f28504c 100644 --- a/old_projects/mug.py +++ b/from_3b1b/old/mug.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from manimlib.imports import * -from old_projects.efvgt import ConfettiSpiril +from from_3b1b.old.efvgt import ConfettiSpiril #revert_to_original_skipping_status @@ -41,8 +41,9 @@ def get_hat(self, pi): hat.flip() vect = LEFT hat.set_fill(RED_D) - hat[0].remove(hat[0][1]) - hat[0].set_fill("#EEE") + 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) diff --git a/old_projects/music_and_measure.py b/from_3b1b/old/music_and_measure.py similarity index 100% rename from old_projects/music_and_measure.py rename to from_3b1b/old/music_and_measure.py diff --git a/old_projects/mvcr.py b/from_3b1b/old/mvcr.py similarity index 100% rename from old_projects/mvcr.py rename to from_3b1b/old/mvcr.py diff --git a/old_projects/nn/image_map b/from_3b1b/old/nn/image_map similarity index 100% rename from old_projects/nn/image_map rename to from_3b1b/old/nn/image_map diff --git a/old_projects/nn/mnist_loader.py b/from_3b1b/old/nn/mnist_loader.py similarity index 100% rename from old_projects/nn/mnist_loader.py rename to from_3b1b/old/nn/mnist_loader.py diff --git a/old_projects/nn/network.py b/from_3b1b/old/nn/network.py similarity index 100% rename from old_projects/nn/network.py rename to from_3b1b/old/nn/network.py diff --git a/old_projects/nn/part1.py b/from_3b1b/old/nn/part1.py similarity index 100% rename from old_projects/nn/part1.py rename to from_3b1b/old/nn/part1.py diff --git a/old_projects/nn/part2.py b/from_3b1b/old/nn/part2.py similarity index 100% rename from old_projects/nn/part2.py rename to from_3b1b/old/nn/part2.py diff --git a/old_projects/nn/part3.py b/from_3b1b/old/nn/part3.py similarity index 100% rename from old_projects/nn/part3.py rename to from_3b1b/old/nn/part3.py diff --git a/old_projects/nn/pretrained_weights_and_biases b/from_3b1b/old/nn/pretrained_weights_and_biases similarity index 100% rename from old_projects/nn/pretrained_weights_and_biases rename to from_3b1b/old/nn/pretrained_weights_and_biases diff --git a/old_projects/nn/pretrained_weights_and_biases_36 b/from_3b1b/old/nn/pretrained_weights_and_biases_36 similarity index 100% rename from old_projects/nn/pretrained_weights_and_biases_36 rename to from_3b1b/old/nn/pretrained_weights_and_biases_36 diff --git a/old_projects/nn/pretrained_weights_and_biases_on_zero b/from_3b1b/old/nn/pretrained_weights_and_biases_on_zero similarity index 100% rename from old_projects/nn/pretrained_weights_and_biases_on_zero rename to from_3b1b/old/nn/pretrained_weights_and_biases_on_zero diff --git a/old_projects/number_line_scene.py b/from_3b1b/old/number_line_scene.py similarity index 100% rename from old_projects/number_line_scene.py rename to from_3b1b/old/number_line_scene.py diff --git a/old_projects/patreon.py b/from_3b1b/old/patreon.py similarity index 100% rename from old_projects/patreon.py rename to from_3b1b/old/patreon.py diff --git a/old_projects/pi_day.py b/from_3b1b/old/pi_day.py similarity index 100% rename from old_projects/pi_day.py rename to from_3b1b/old/pi_day.py diff --git a/old_projects/playground_counting_in_binary.py b/from_3b1b/old/playground_counting_in_binary.py similarity index 100% rename from old_projects/playground_counting_in_binary.py rename to from_3b1b/old/playground_counting_in_binary.py diff --git a/old_projects/putnam.py b/from_3b1b/old/putnam.py similarity index 100% rename from old_projects/putnam.py rename to from_3b1b/old/putnam.py diff --git a/old_projects/pythagorean_proof.py b/from_3b1b/old/pythagorean_proof.py similarity index 100% rename from old_projects/pythagorean_proof.py rename to from_3b1b/old/pythagorean_proof.py diff --git a/old_projects/qa_round_two.py b/from_3b1b/old/qa_round_two.py similarity index 99% rename from old_projects/qa_round_two.py rename to from_3b1b/old/qa_round_two.py index 6cfdf5fbdf..ff64ff1777 100644 --- a/old_projects/qa_round_two.py +++ b/from_3b1b/old/qa_round_two.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from manimlib.imports import * -from old_projects.efvgt import get_confetti_animations +from from_3b1b.old.efvgt import get_confetti_animations class Test(Scene): diff --git a/old_projects/quat3d.py b/from_3b1b/old/quat3d.py similarity index 99% rename from old_projects/quat3d.py rename to from_3b1b/old/quat3d.py index 6b74dcc901..28f62a723a 100644 --- a/old_projects/quat3d.py +++ b/from_3b1b/old/quat3d.py @@ -1,5 +1,5 @@ from manimlib.imports import * -from old_projects.quaternions import * +from from_3b1b.old.quaternions import * W_COLOR = YELLOW I_COLOR = GREEN diff --git a/old_projects/quaternions.py b/from_3b1b/old/quaternions.py similarity index 99% rename from old_projects/quaternions.py rename to from_3b1b/old/quaternions.py index e456782684..6d2a12dba4 100644 --- a/old_projects/quaternions.py +++ b/from_3b1b/old/quaternions.py @@ -3812,7 +3812,7 @@ def construct(self): def add_axes(self): axes = Axes( - number_line_config={ + axis_config={ "unit_size": 2, "color": WHITE, } @@ -4336,7 +4336,7 @@ def get_simple_axes(self, label, color): class SimpleImaginaryQuaternionAxes(SpecialThreeDScene): def construct(self): self.three_d_axes_config.update({ - "number_line_config": {"unit_size": 2}, + "axis_config": {"unit_size": 2}, "x_min": -2, "x_max": 2, "y_min": -2, diff --git a/old_projects/sphere_area.py b/from_3b1b/old/sphere_area.py similarity index 99% rename from old_projects/sphere_area.py rename to from_3b1b/old/sphere_area.py index 84150ae107..5d3c86a17b 100644 --- a/old_projects/sphere_area.py +++ b/from_3b1b/old/sphere_area.py @@ -1458,7 +1458,7 @@ def construct(self): def recreate_cross_section(self): axes = Axes( - number_line_config={ + axis_config={ "unit_size": 2, } ) diff --git a/from_3b1b/old/spirals.py b/from_3b1b/old/spirals.py new file mode 100644 index 0000000000..ed5b3cb939 --- /dev/null +++ b/from_3b1b/old/spirals.py @@ -0,0 +1,4931 @@ +from manimlib.imports import * +import json +import numbers + + +OUTPUT_DIRECTORY = "spirals" +INV_113_MOD_710 = 377 # Inverse of 113 mode 710 +INV_7_MOD_44 = 19 + + +def is_prime(n): + if n < 2: + return False + for k in range(2, int(np.sqrt(n)) + 1): + if n % k == 0: + return False + return True + + +def generate_prime_list(*args): + if len(args) == 1: + start, stop = 2, args[0] + elif len(args) == 2: + start, stop = args + start = max(start, 2) + else: + raise TypeError("generate_prime_list takes 1 or 2 arguments") + + result = [ + n for n in range(start, stop) + if is_prime(n) + ] + return result + + +def get_gcd(x, y): + while y > 0: + x, y = y, x % y + return x + + +def read_in_primes(max_N=None): + if max_N is None: + max_N = int(1e7) + + if max_N < 1e5: + file = "primes_1e5.json" + elif max_N < 1e6: + file = "primes_1e6.json" + else: + file = "primes_1e7.json" + + with open(os.path.join("assets", file)) as fp: + primes = np.array(json.load(fp)) + return primes[primes <= max_N] + + +class SpiralScene(MovingCameraScene): + CONFIG = { + "axes_config": { + "axis_config": { + "stroke_width": 1.5, + } + }, + "default_dot_color": TEAL, + "p_spiral_width": 6, + } + + def setup(self): + super().setup() + self.axes = Axes(**self.axes_config) + self.add(self.axes) + + def get_v_spiral(self, sequence, axes=None, box_width=None): + if axes is None: + axes = self.axes + if box_width is None: + unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)), + box_width = max( + 0.2 / (-np.log10(unit) + 1), + 0.02, + ) + + return VGroup(*[ + Square( + side_length=box_width, + fill_color=self.default_dot_color, + fill_opacity=1, + stroke_width=0, + ).move_to(self.get_polar_point(n, n, axes)) + for n in sequence + ]) + + def get_p_spiral(self, sequence, axes=None): + if axes is None: + axes = self.axes + result = PMobject( + color=self.default_dot_color, + stroke_width=self.p_spiral_width, + ) + result.add_points([ + self.get_polar_point(n, n, axes) + for n in sequence + ]) + return result + + def get_prime_v_spiral(self, max_N, **kwargs): + primes = read_in_primes(max_N) + return self.get_v_spiral(primes, **kwargs) + + def get_prime_p_spiral(self, max_N, **kwargs): + primes = read_in_primes(max_N) + return self.get_p_spiral(primes, **kwargs) + + def get_polar_point(self, r, theta, axes=None): + if axes is None: + axes = self.axes + return axes.c2p(r * np.cos(theta), r * np.sin(theta)) + + def set_scale(self, scale, + axes=None, + spiral=None, + to_shrink=None, + min_box_width=0.05, + target_p_spiral_width=None, + added_anims=[], + run_time=3): + if axes is None: + axes = self.axes + if added_anims is None: + added_anims = [] + sf = self.get_scale_factor(scale, axes) + + anims = [] + for mob in [axes, spiral, to_shrink]: + if mob is None: + continue + mob.generate_target() + mob.target.scale(sf, about_point=ORIGIN) + if mob is spiral: + if isinstance(mob, VMobject): + old_width = mob[0].get_width() + for submob in mob.target: + submob.set_width(max( + old_width * sf, + min_box_width, + )) + elif isinstance(mob, PMobject): + if target_p_spiral_width is not None: + mob.target.set_stroke_width(target_p_spiral_width) + anims.append(MoveToTarget(mob)) + anims += added_anims + + if run_time == 0: + for anim in anims: + anim.begin() + anim.update(1) + anim.finish() + else: + self.play( + *anims, + run_time=run_time, + rate_func=lambda t: interpolate( + smooth(t), + smooth(t)**(sf**(0.5)), + t, + ) + ) + + def get_scale_factor(self, target_scale, axes=None): + if axes is None: + axes = self.axes + unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)) + return 1 / (target_scale * unit) + + def get_labels(self, sequence, scale_func=np.sqrt): + labels = VGroup() + for n in sequence: + label = Integer(n) + label.set_stroke(width=0, background=True) + label.scale(scale_func(n)) + label.next_to( + self.get_polar_point(n, n), UP, + buff=0.5 * label.get_height(), + ) + labels.add(label) + return labels + + def get_prime_labels(self, max_N): + primes = read_in_primes(max_N) + return self.get_labels(primes) + + +# Scenes + +class AltTitle(Scene): + def construct(self): + title_text = """ + How pretty but pointless patterns\\\\ + in polar plots of primes\\\\ + prompt pretty important ponderings\\\\ + on properties of those primes. + """ + words = [w + " " for w in title_text.split(" ") if w] + title = TextMobject(*words) + title.set_width(FRAME_WIDTH - 1) + + title[2:5].set_color(TEAL) + title[12:15].set_color(YELLOW) + title.set_stroke(BLACK, 5, background=True) + + image = ImageMobject("PrimeSpiral") + image.set_height(FRAME_HEIGHT) + rect = FullScreenFadeRectangle(fill_opacity=0.25) + + self.add(image, rect) + + for word in title: + self.play( + FadeIn( + word, run_time=0.05 * len(word), + lag_ratio=0.4, + ) + ) + self.wait() + + +class HoldUpMathExchange(TeacherStudentsScene): + def construct(self): + title = TextMobject("Mathematics Stack Exchange") + title.scale(1.5) + title.to_edge(UP) + + self.add(title) + self.play(self.teacher.change, "raise_right_hand", ORIGIN), + self.change_all_student_modes("thinking", look_at_arg=ORIGIN) + self.wait(3) + self.change_all_student_modes("confused", look_at_arg=ORIGIN) + self.wait(3) + + +class MathExchangeNames(Scene): + def construct(self): + names = VGroup( + TextMobject("dwymark"), + TextMobject("Greg Martin"), + ) + names.arrange(DOWN, buff=1) + for name in names: + self.play(FadeInFrom(name, RIGHT)) + self.wait() + + +class MathExchange(ExternallyAnimatedScene): + pass + + +class PrimesAndPi(Scene): + def construct(self): + self.show_primes() + self.show_rational_approximations() + + def show_primes(self): + n_rows = 10 + n_cols = 10 + matrix = IntegerMatrix([ + [n_cols * x + y for y in range(n_cols)] + for x in range(n_rows) + ]) + numbers = matrix.get_entries() + primes = VGroup(*filter( + lambda m: is_prime(m.get_value()), + numbers, + )) + non_primes = VGroup(*filter( + lambda m: not is_prime(m.get_value()), + numbers + )) + + self.add(numbers) + + self.play( + LaggedStart(*[ + ApplyFunction( + lambda m: m.set_color(TEAL).scale(1.2), + prime + ) + for prime in primes + ]), + non_primes.set_opacity, 0.25, + run_time=2, + ) + self.wait() + + self.numbers = numbers + + def show_rational_approximations(self): + numbers = self.numbers + + approxs = TexMobject( + "{22 \\over 7} &=", "{:.12}\\dots\\\\".format(22 / 7), + "{355 \\over 113} &=", "{:.12}\\dots\\\\".format(355 / 113), + "\\pi &=", "{:.12}\\dots\\\\".format(PI), + ) + approxs[:2].shift(MED_LARGE_BUFF * UP) + approxs[-2:].shift(MED_LARGE_BUFF * DOWN) + approxs[-2:].set_color(YELLOW) + approxs[1][:4].set_color(YELLOW) + approxs[3][:8].set_color(YELLOW) + approxs.scale(1.5) + + randy = Randolph(color=YELLOW, height=1) + randy.move_to(approxs[-2][0], RIGHT) + approxs[-2][0].set_opacity(0) + + self.play( + LaggedStartMap(FadeOutAndShiftDown, numbers), + LaggedStartMap(FadeIn, approxs), + FadeIn(randy) + ) + self.play(Blink(randy)) + self.play(randy.change, "pondering", UR) + self.wait() + + +class RefresherOnPolarCoordinates(MovingCameraScene): + CONFIG = { + "x_color": GREEN, + "y_color": RED, + "r_color": YELLOW, + "theta_color": LIGHT_PINK, + } + + def construct(self): + self.show_xy_coordinates() + self.transition_to_polar_grid() + self.show_polar_coordinates() + + self.show_all_nn_tuples() + + def show_xy_coordinates(self): + plane = NumberPlane() + plane.add_coordinates() + + x = 3 * np.cos(PI / 6) + y = 3 * np.sin(PI / 6) + + point = plane.c2p(x, y) + xp = plane.c2p(x, 0) + origin = plane.c2p(0, 0) + + x_color = self.x_color + y_color = self.y_color + + x_line = Line(origin, xp, color=x_color) + y_line = Line(xp, point, color=y_color) + + dot = Dot(point) + + coord_label = self.get_coord_label(0, 0, x_color, y_color) + x_coord = coord_label.x_coord + y_coord = coord_label.y_coord + + coord_label.next_to(dot, UR, SMALL_BUFF) + + x_brace = Brace(x_coord, UP) + y_brace = Brace(y_coord, UP) + x_brace.add(x_brace.get_tex("x").set_color(x_color)) + y_brace.add(y_brace.get_tex("y").set_color(y_color)) + x_brace.add_updater(lambda m: m.next_to(x_coord, UP, SMALL_BUFF)) + y_brace.add_updater(lambda m: m.next_to(y_coord, UP, SMALL_BUFF)) + + self.add(plane) + self.add(dot, coord_label) + self.add(x_brace, y_brace) + + coord_label.add_updater( + lambda m: m.next_to(dot, UR, SMALL_BUFF) + ) + + self.play( + ShowCreation(x_line), + ChangeDecimalToValue(x_coord, x), + UpdateFromFunc( + dot, + lambda d: d.move_to(x_line.get_end()), + ), + run_time=2, + ) + self.play( + ShowCreation(y_line), + ChangeDecimalToValue(y_coord, y), + UpdateFromFunc( + dot, + lambda d: d.move_to(y_line.get_end()), + ), + run_time=2, + ) + self.wait() + + self.xy_coord_mobjects = VGroup( + x_line, y_line, coord_label, + x_brace, y_brace, + ) + self.plane = plane + self.dot = dot + + def transition_to_polar_grid(self): + self.polar_grid = self.get_polar_grid() + self.add(self.polar_grid, self.dot) + self.play( + FadeOut(self.xy_coord_mobjects), + FadeOut(self.plane), + ShowCreation(self.polar_grid, run_time=2), + ) + self.wait() + + def show_polar_coordinates(self): + dot = self.dot + plane = self.plane + origin = plane.c2p(0, 0) + + r_color = self.r_color + theta_color = self.theta_color + + r_line = Line(origin, dot.get_center()) + r_line.set_color(r_color) + r_value = r_line.get_length() + theta_value = r_line.get_angle() + + coord_label = self.get_coord_label(r_value, theta_value, r_color, theta_color) + r_coord = coord_label.x_coord + theta_coord = coord_label.y_coord + + coord_label.add_updater(lambda m: m.next_to(dot, UP, buff=SMALL_BUFF)) + r_coord.add_updater(lambda d: d.set_value( + get_norm(dot.get_center()) + )) + theta_coord.add_background_rectangle() + theta_coord.add_updater(lambda d: d.set_value( + (angle_of_vector(dot.get_center()) % TAU) + )) + coord_label[-1].add_updater( + lambda m: m.next_to(theta_coord, RIGHT, SMALL_BUFF) + ) + + non_coord_parts = VGroup(*[ + part + for part in coord_label + if part not in [r_coord, theta_coord] + ]) + + r_label = TexMobject("r") + r_label.set_color(r_color) + r_label.add_updater(lambda m: m.next_to(r_coord, UP)) + theta_label = TexMobject("\\theta") + theta_label.set_color(theta_color) + theta_label.add_updater(lambda m: m.next_to(theta_coord, UP)) + + r_coord_copy = r_coord.copy() + r_coord_copy.add_updater( + lambda m: m.next_to(r_line.get_center(), UL, buff=0) + ) + + degree_label = DecimalNumber(0, num_decimal_places=1, unit="^\\circ") + arc = Arc(radius=1, angle=theta_value) + arc.set_color(theta_color) + degree_label.set_color(theta_color) + + # Show r + self.play( + ShowCreation(r_line, run_time=2), + ChangeDecimalToValue(r_coord_copy, r_value, run_time=2), + VFadeIn(r_coord_copy, run_time=0.5), + ) + r_coord.set_value(r_value) + self.add(non_coord_parts, r_coord_copy) + self.play( + FadeIn(non_coord_parts), + ReplacementTransform(r_coord_copy, r_coord), + FadeInFromDown(r_label), + ) + self.wait() + + # Show theta + degree_label.next_to(arc.get_start(), UR, SMALL_BUFF) + line = r_line.copy() + line.rotate(-theta_value, about_point=ORIGIN) + line.set_color(theta_color) + self.play( + ShowCreation(arc), + Rotate(line, theta_value, about_point=ORIGIN), + VFadeInThenOut(line), + ChangeDecimalToValue(degree_label, theta_value / DEGREES), + ) + self.play( + degree_label.scale, 0.9, + degree_label.move_to, theta_coord, + FadeInFromDown(theta_label), + ) + self.wait() + + degree_cross = Cross(degree_label) + radians_word = TextMobject("in radians") + radians_word.scale(0.9) + radians_word.set_color(theta_color) + radians_word.add_background_rectangle() + radians_word.add_updater( + lambda m: m.next_to(theta_label, RIGHT, aligned_edge=DOWN) + ) + + self.play(ShowCreation(degree_cross)) + self.play( + FadeOutAndShift( + VGroup(degree_label, degree_cross), + DOWN + ), + FadeIn(theta_coord) + ) + self.play(FadeIn(radians_word)) + self.wait() + + # Move point around + r_line.add_updater( + lambda l: l.put_start_and_end_on(ORIGIN, dot.get_center()) + ) + theta_tracker = ValueTracker(0) + theta_tracker.add_updater( + lambda m: m.set_value(r_line.get_angle() % TAU) + ) + self.add(theta_tracker) + arc.add_updater( + lambda m: m.become( + self.get_arc(theta_tracker.get_value()) + ) + ) + + self.add(coord_label) + for angle in [PI - theta_value, PI - 0.001, -TAU + 0.002]: + self.play( + Rotate(dot, angle, about_point=ORIGIN), + run_time=3, + ) + self.wait() + self.play( + FadeOut(coord_label), + FadeOut(r_label), + FadeOut(theta_label), + FadeOut(radians_word), + FadeOut(r_line), + FadeOut(arc), + FadeOut(dot), + ) + + self.dot = dot + self.r_line = r_line + self.arc = arc + self.theta_tracker = theta_tracker + + def show_all_nn_tuples(self): + dot = self.dot + arc = self.arc + r_line = self.r_line + theta_tracker = self.theta_tracker + + primes = generate_prime_list(20) + non_primes = list(range(1, 20)) + for prime in primes: + non_primes.remove(prime) + + pp_points = VGroup(*map(self.get_nn_point, primes)) + pp_points[0][1].shift(0.3 * LEFT + SMALL_BUFF * UP) + np_points = VGroup(*map(self.get_nn_point, non_primes)) + pp_points.set_color(TEAL) + np_points.set_color(WHITE) + pp_points.set_stroke(BLACK, 4, background=True) + np_points.set_stroke(BLACK, 4, background=True) + + frame = self.camera_frame + self.play( + ApplyMethod(frame.scale, 2), + LaggedStartMap( + FadeInFromDown, pp_points + ), + run_time=2 + ) + self.wait() + self.play(LaggedStartMap(FadeIn, np_points)) + self.play(frame.scale, 0.5) + self.wait() + + # Talk about 1 + one = np_points[0] + dot.move_to(self.get_polar_point(1, 1)) + self.add(dot) + theta_tracker.clear_updaters() + theta_tracker.set_value(1) + # r_line = Line(ORIGIN, one.dot.get_center()) + # r_line.set_color(self.r_color) + # pre_arc = Line(RIGHT, UR, color=self.r_color) + # theta_tracker = ValueTracker(1) + # arc = always_redraw(lambda: self.get_arc(theta_tracker.get_value())) + + one_rect = SurroundingRectangle(one) + one_r_rect = SurroundingRectangle(one.label[1]) + one_theta_rect = SurroundingRectangle(one.label[3]) + one_theta_rect.set_color(self.theta_color) + + self.play(ShowCreation(one_rect)) + self.add(r_line, np_points, pp_points, one_rect) + self.play( + ReplacementTransform(one_rect, one_r_rect), + ShowCreation(r_line) + ) + self.wait() + # self.play(TransformFromCopy(r_line, pre_arc)) + # self.add(pre_arc, one) + self.play( + ReplacementTransform( + Line(*r_line.get_start_and_end()), arc + ), + ReplacementTransform(one_r_rect, one_theta_rect) + ) + self.add(arc, one, one_theta_rect) + self.play(FadeOut(one_theta_rect)) + self.wait() + + # Talk about 2, 3 then 4 + for n in [2, 3, 4]: + self.play( + Rotate(dot, 1, about_point=ORIGIN), + theta_tracker.set_value, n, + ) + self.wait() + self.play(dot.move_to, self.get_polar_point(n, n)) + self.wait() + + # Zoom out and show spiral + big_anim = Succession(*3 * [Animation(Mobject())], *it.chain(*[ + [ + AnimationGroup( + Rotate(dot, 1, about_point=ORIGIN), + ApplyMethod(theta_tracker.set_value, n), + ), + ApplyMethod(dot.move_to, self.get_polar_point(n, n)) + ] + for n in [5, 6, 7, 8, 9] + ])) + + spiral = ParametricFunction( + lambda t: self.get_polar_point(t, t), + t_min=0, + t_max=25, + stroke_width=1.5, + ) + + # self.add(spiral, pp_points, np_points) + + self.polar_grid.generate_target() + for mob in self.polar_grid: + if not isinstance(mob[0], Integer): + mob.set_stroke(width=1) + + self.play( + frame.scale, 3, + big_anim, + run_time=10, + ) + self.play( + # ApplyMethod( + # frame.scale, 1.5, + # run_time=2, + # rate_func=lambda t: smooth(t, 2) + # ), + ShowCreation( + spiral, + run_time=4, + ), + FadeOut(r_line), + FadeOut(arc), + FadeOut(dot), + # MoveToTarget(self.polar_grid) + ) + self.wait() + + # + def get_nn_point(self, n): + point = self.get_polar_point(n, n) + dot = Dot(point) + coord_label = self.get_coord_label( + n, n, + include_background_rectangle=False, + num_decimal_places=0 + ) + coord_label.next_to(dot, UR, buff=0) + result = VGroup(dot, coord_label) + result.dot = dot + result.label = coord_label + return result + + def get_polar_grid(self, radius=25): + plane = self.plane + axes = VGroup( + Line(radius * DOWN, radius * UP), + Line(radius * LEFT, radius * RIGHT), + ) + axes.set_stroke(width=2) + circles = VGroup(*[ + Circle(color=BLUE, stroke_width=1, radius=r) + for r in range(1, int(radius)) + ]) + rays = VGroup(*[ + Line( + ORIGIN, radius * RIGHT, + color=BLUE, + stroke_width=1, + ).rotate(angle, about_point=ORIGIN) + for angle in np.arange(0, TAU, TAU / 16) + ]) + labels = VGroup(*[ + Integer(n).scale(0.5).next_to( + plane.c2p(n, 0), DR, SMALL_BUFF + ) + for n in range(1, int(radius)) + ]) + + return VGroup( + circles, rays, labels, axes, + ) + + def get_coord_label(self, + x=0, + y=0, + x_color=WHITE, + y_color=WHITE, + include_background_rectangle=True, + **decimal_kwargs): + coords = VGroup() + for n in x, y: + if isinstance(n, numbers.Number): + coord = DecimalNumber(n, **decimal_kwargs) + elif isinstance(n, str): + coord = TexMobject(n) + else: + raise Exception("Invalid type") + coords.add(coord) + + x_coord, y_coord = coords + x_coord.set_color(x_color) + y_coord.set_color(y_color) + + coord_label = VGroup( + TexMobject("("), x_coord, + TexMobject(","), y_coord, + TexMobject(")") + ) + coord_label.arrange(RIGHT, buff=SMALL_BUFF) + coord_label[2].align_to(coord_label[0], DOWN) + + coord_label.x_coord = x_coord + coord_label.y_coord = y_coord + if include_background_rectangle: + coord_label.add_background_rectangle() + return coord_label + + def get_polar_point(self, r, theta): + plane = self.plane + return plane.c2p(r * np.cos(theta), r * np.sin(theta)) + + def get_arc(self, theta, r=1, color=None): + if color is None: + color = self.theta_color + return ParametricFunction( + lambda t: self.get_polar_point(1 + 0.025 * t, t), + t_min=0, + t_max=theta, + dt=0.25, + color=color, + stroke_width=3, + ) + # return Arc( + # angle=theta, + # radius=r, + # stroke_color=color, + # ) + + +class IntroducePolarPlot(RefresherOnPolarCoordinates): + def construct(self): + self.plane = NumberPlane() + grid = self.get_polar_grid() + title = TextMobject("Polar coordinates") + title.scale(3) + title.set_stroke(BLACK, 10, background=True) + title.to_edge(UP) + + self.add(grid, title) + self.play( + ShowCreation(grid, lag_ratio=0.1), + run_time=3, + ) + + +class ReplacePolarCoordinatesWithPrimes(RefresherOnPolarCoordinates): + def construct(self): + coords, p_coords = [ + self.get_coord_label( + *pair, + x_color=self.r_color, + y_color=self.theta_color, + ).scale(2) + for pair in [("r", "\\theta"), ("p", "p")] + ] + p_coords.x_coord.set_color(LIGHT_GREY) + p_coords.y_coord.set_color(LIGHT_GREY) + + some_prime = TextMobject("Some prime") + some_prime.scale(1.5) + some_prime.next_to(p_coords.get_left(), DOWN, buff=1.5) + arrows = VGroup(*[ + Arrow( + some_prime.get_top(), coord.get_bottom(), + stroke_width=5, + tip_length=0.4 + ) + for coord in [p_coords.x_coord, p_coords.y_coord] + ]) + + equals = TexMobject("=") + equals.next_to(p_coords, LEFT) + + self.add(coords) + self.wait() + self.play( + coords.next_to, equals, LEFT, + FadeIn(equals), + FadeIn(p_coords), + ) + self.play( + FadeInFromDown(some_prime), + ShowCreation(arrows), + ) + self.wait() + + +class IntroducePrimePatterns(SpiralScene): + CONFIG = { + "small_n_primes": 25000, + "big_n_primes": 1000000, + "axes_config": { + "x_min": -25, + "x_max": 25, + "y_min": -25, + "y_max": 25, + }, + "spiral_scale": 3e3, + "ray_scale": 1e5, + } + + def construct(self): + self.slowly_zoom_out() + self.show_clumps_of_four() + + def slowly_zoom_out(self): + zoom_time = 8 + + prime_spiral = self.get_prime_p_spiral(self.small_n_primes) + prime_spiral.set_stroke_width(25) + self.add(prime_spiral) + + self.set_scale(3, spiral=prime_spiral) + self.wait() + self.set_scale( + self.spiral_scale, + spiral=prime_spiral, + target_p_spiral_width=8, + run_time=zoom_time, + ) + self.wait() + + self.remove(prime_spiral) + prime_spiral = self.get_prime_p_spiral(self.big_n_primes) + prime_spiral.set_stroke_width(8) + self.set_scale( + self.ray_scale, + spiral=prime_spiral, + target_p_spiral_width=4, + run_time=zoom_time, + ) + self.wait() + + def show_clumps_of_four(self): + line_groups = VGroup() + for n in range(71): + group = VGroup() + for k in [-3, -1, 1, 3]: + r = ((10 * n + k) * INV_113_MOD_710) % 710 + group.add(self.get_arithmetic_sequence_line( + 710, r, self.big_n_primes + )) + line_groups.add(group) + + line_groups.set_stroke(YELLOW, 2, opacity=0.5) + + self.play(ShowCreation(line_groups[0])) + for g1, g2 in zip(line_groups, line_groups[1:5]): + self.play( + FadeOut(g1), + ShowCreation(g2) + ) + + self.play( + FadeOut(line_groups[4]), + LaggedStartMap( + VFadeInThenOut, + line_groups[4:], + lag_ratio=0.5, + run_time=5, + ) + ) + self.wait() + + def get_arithmetic_sequence_line(self, N, r, max_val, skip_factor=5): + line = VMobject() + line.set_points_smoothly([ + self.get_polar_point(x, x) + for x in range(r, max_val, skip_factor * N) + ]) + return line + + +class AskWhat(TeacherStudentsScene): + def construct(self): + screen = self.screen + self.student_says( + "I'm sorry,\\\\what?!?", + target_mode="angry", + look_at_arg=screen, + student_index=2, + added_anims=[ + self.teacher.change, "happy", screen, + self.students[0].change, "confused", screen, + self.students[1].change, "confused", screen, + ] + ) + self.wait(3) + + +class CountSpirals(IntroducePrimePatterns): + CONFIG = { + "count_sound": "pen_click.wav", + } + + def construct(self): + prime_spiral = self.get_prime_p_spiral(self.small_n_primes) + + self.add(prime_spiral) + self.set_scale( + self.spiral_scale, + spiral=prime_spiral, + run_time=0, + ) + + spiral_lines = self.get_all_primative_arithmetic_lines( + 44, self.small_n_primes, INV_7_MOD_44, + ) + spiral_lines.set_stroke(YELLOW, 2, opacity=0.5) + + counts = VGroup() + for n, spiral in zip(it.count(1), spiral_lines): + count = Integer(n) + count.move_to(spiral.point_from_proportion(0.25)) + counts.add(count) + + run_time = 3 + self.play( + ShowIncreasingSubsets(spiral_lines), + ShowSubmobjectsOneByOne(counts), + run_time=run_time, + rate_func=linear, + ) + self.add_count_clicks(len(spiral_lines), run_time) + self.play( + counts[-1].scale, 3, + counts[-1].set_stroke, BLACK, 5, {"background": True}, + ) + self.wait() + + def get_all_primative_arithmetic_lines(self, N, max_val, mult_factor): + lines = VGroup() + for r in range(1, N): + if get_gcd(N, r) == 1: + lines.add( + self.get_arithmetic_sequence_line(N, (mult_factor * r) % N, max_val) + ) + return lines + + def add_count_clicks(self, N, time, rate_func=linear): + alphas = np.arange(0, 1, 1 / N) + if rate_func is linear: + delays = time * alphas + else: + delays = time * np.array([ + binary_search(rate_func, alpha, 0, 1) + for alpha in alphas + ]) + + for delay in delays: + self.add_sound( + self.count_sound, + time_offset=-delay, + gain=-15, + ) + + +class CountRays(CountSpirals): + def construct(self): + prime_spiral = self.get_prime_p_spiral(self.big_n_primes) + + self.add(prime_spiral) + self.set_scale( + self.ray_scale, + spiral=prime_spiral, + run_time=0, + ) + + spiral_lines = self.get_all_primative_arithmetic_lines( + 710, self.big_n_primes, INV_113_MOD_710, + ) + spiral_lines.set_stroke(YELLOW, 2, opacity=0.5) + + counts = VGroup() + for n, spiral in zip(it.count(1), spiral_lines): + count = Integer(n) + count.move_to(spiral.point_from_proportion(0.25)) + counts.add(count) + + run_time = 6 + self.play( + ShowIncreasingSubsets(spiral_lines), + ShowSubmobjectsOneByOne(counts), + run_time=run_time, + rate_func=smooth, + ) + self.add_count_clicks(len(spiral_lines), run_time, rate_func=smooth) + self.play( + counts[-1].scale, 3, + counts[-1].set_stroke, BLACK, 5, {"background": True}, + ) + self.wait() + self.play(FadeOut(spiral_lines)) + self.wait() + + +class AskAboutRelationToPrimes(TeacherStudentsScene): + def construct(self): + numbers = TextMobject("20, 280") + arrow = Arrow(LEFT, RIGHT) + primes = TextMobject("2, 3, 5, 7, 11, \\dots") + q_marks = TextMobject("???") + q_marks.set_color(YELLOW) + + group = VGroup(primes, arrow, numbers) + group.arrange(RIGHT) + q_marks.next_to(arrow, UP) + group.add(q_marks) + group.scale(1.5) + group.next_to(self.pi_creatures, UP, LARGE_BUFF) + + self.play( + self.get_student_changes( + *3 * ["maybe"], + look_at_arg=numbers, + ), + self.teacher.change, "maybe", numbers, + ShowCreation(arrow), + FadeInFrom(numbers, RIGHT) + ) + self.play( + FadeInFrom(primes, LEFT), + ) + self.play( + LaggedStartMap(FadeInFromDown, q_marks[0]), + Blink(self.teacher) + ) + self.wait(3) + + +class ZoomOutOnPrimesWithNumbers(IntroducePrimePatterns): + CONFIG = { + "n_labeled_primes": 1000, + "big_n_primes": int(5e6), + "thicknesses": [8, 3, 2], + "thicker_target": False, + } + + def construct(self): + zoom_time = 20 + + prime_spiral = self.get_prime_p_spiral(self.big_n_primes) + prime_spiral.set_stroke_width(25) + + prime_labels = self.get_prime_labels(self.n_labeled_primes) + + self.add(prime_spiral) + self.add(prime_labels) + + scales = [self.spiral_scale, self.ray_scale, 5e5] + thicknesses = self.thicknesses + + for scale, tp in zip(scales, thicknesses): + kwargs = { + "spiral": prime_spiral, + "to_shrink": prime_labels, + "run_time": zoom_time, + "target_p_spiral_width": tp, + } + if self.thicker_target: + kwargs["target_p_spiral_width"] += 1 + self.set_scale(scale, **kwargs) + prime_spiral.set_stroke_width(tp) + self.wait() + self.remove(prime_labels) + + +class ThickZoomOutOnPrimesWithNumbers(ZoomOutOnPrimesWithNumbers): + CONFIG = { + # The only purpose of this scene is for overlay + # with the last one to smooth things out. + "thicker_target": True, + } + + +class HighlightGapsInSpirals(IntroducePrimePatterns): + def construct(self): + self.setup_spiral() + + max_n_tracker = ValueTracker(0) + get_max_n = max_n_tracker.get_value + gaps = always_redraw(lambda: VGroup(*[ + self.get_highlighted_gap(n - 1, n + 1, get_max_n()) + for n in [11, 33] + ])) + + self.add(gaps) + self.play(max_n_tracker.set_value, 25000, run_time=5) + gaps.clear_updaters() + self.play(FadeOut(gaps)) + + def setup_spiral(self): + p_spiral = self.get_p_spiral(read_in_primes(self.small_n_primes)) + self.add(p_spiral) + self.set_scale( + scale=self.spiral_scale, + spiral=p_spiral, + target_p_spiral_width=8, + run_time=0, + ) + + def get_highlighted_gap(self, n1, n2, max_n): + l1, l2 = [ + [ + self.get_polar_point(k, k) + for k in range(INV_7_MOD_44 * n, int(max_n), 5 * 44) + ] + for n in (n1, n2) + ] + + if len(l1) == 0 or len(l2) == 0: + return VectorizedPoint() + + result = VMobject() + result.set_points_as_corners( + [*l1, *reversed(l2)] + ) + result.make_smooth() + + result.set_stroke(GREY, width=0) + result.set_fill(DARK_GREY, 1) + + return result + + +class QuestionIsMisleading(TeacherStudentsScene): + def construct(self): + self.student_says( + "Whoa, is this some\\\\divine hidden structure\\\\in the primes?", + target_mode="surprised", + student_index=0, + added_anims=[ + self.students[1].change, "pondering", + self.students[2].change, "pondering", + ] + ) + self.wait(2) + + self.students[0].bubble = None + self.teacher_says( + "Er...not exactly", + bubble_kwargs={"width": 3, "height": 2}, + target_mode="guilty" + ) + self.wait(3) + + +class JustPrimesLabel(Scene): + def construct(self): + text = TextMobject("Just the primes") + text.scale(2) + text.to_corner(UL) + self.play(Write(text)) + self.wait(3) + self.play(FadeOutAndShift(text, DOWN)) + + +class DirichletComingUp(Scene): + def construct(self): + image = ImageMobject("Dirichlet") + image.set_height(3) + words = TextMobject( + "Coming up: \\\\", "Dirichlet's theorem", + alignment="", + ) + words.set_color_by_tex("Dirichlet's", YELLOW) + words.scale(1.5) + words.next_to(image, RIGHT) + words.set_stroke(BLACK, 8, background=True) + Group(words, image).center() + + self.play( + FadeInFrom(image, RIGHT), + FadeInFrom(words, LEFT), + ) + self.wait() + + +class ImagineYouFoundIt(TeacherStudentsScene): + def construct(self): + you = self.students[1] + others = VGroup( + self.students[0], + self.students[2], + self.teacher, + ) + bubble = you.get_bubble(direction=LEFT) + bubble[-1].set_fill(GREEN_SCREEN, 1) + + you_label = TextMobject("You") + arrow = Vector(DOWN) + arrow.next_to(you, UP) + you_label.next_to(arrow, UP) + + self.play( + you.change, "hesitant", you_label, + FadeInFromDown(you_label), + GrowArrow(arrow), + others.set_opacity, 0.25, + ) + self.play(Blink(you)) + self.play( + FadeIn(bubble), + FadeOut(you_label), + FadeOut(arrow), + you.change, "pondering", + ) + self.play(you.look_at, bubble.get_corner(UR)) + self.play(Blink(you)) + self.wait() + self.play(you.change, "hooray") + self.play(Blink(you)) + self.wait() + self.play(you.change, "sassy", bubble.get_top()) + self.wait(6) + + +class ShowSpiralsForWholeNumbers(CountSpirals): + CONFIG = { + "max_prime": 10000, + "scale_44": 1e3, + "scale_6": 10, + "n_labels": 100, + "axes_config": { + "x_min": -50, + "x_max": 50, + "y_min": -50, + "y_max": 50, + }, + } + + def construct(self): + self.zoom_out_with_whole_numbers() + self.count_44_spirals() + self.zoom_back_in_to_6() + + def zoom_out_with_whole_numbers(self): + wholes = self.get_p_spiral(range(self.max_prime)) + primes = self.get_prime_p_spiral(self.max_prime) + + wholes.set_color(YELLOW) + wholes.set_stroke_width(20) + primes.set_stroke_width(20) + spiral = PGroup(wholes, primes) + + labels = self.get_labels(range(1, self.n_labels)) + + self.add(spiral, labels) + self.set_scale( + self.scale_44, + spiral=spiral, + to_shrink=labels, + target_p_spiral_width=6, + run_time=10, + ) + self.wait(2) + + self.spiral = spiral + self.labels = labels + + def count_44_spirals(self): + curr_spiral = self.spiral + + new_spirals = PGroup(*[ + self.get_p_spiral(range( + (INV_7_MOD_44 * k) % 44, self.max_prime, 44 + )) + for k in range(44) + ]) + new_spirals.set_color(YELLOW) + + counts = VGroup() + for n, spiral in zip(it.count(1), new_spirals): + count = Integer(n) + count.scale(2) + count.move_to(spiral.points[50]) + counts.add(count) + + self.remove(curr_spiral) + run_time = 3 + self.play( + ShowIncreasingSubsets(new_spirals), + ShowSubmobjectsOneByOne(counts), + run_time=run_time, + rate_func=linear, + ) + self.add_count_clicks(44, run_time) + self.play( + counts[-1].scale, 2, {"about_edge": DL}, + counts[-1].set_stroke, BLACK, 5, {"background": True}, + ) + self.wait() + self.play( + FadeOut(counts[-1]), + FadeOut(new_spirals), + FadeIn(curr_spiral), + ) + + def zoom_back_in_to_6(self): + spiral = self.spiral + + self.rescale_labels(self.labels) + self.set_scale( + self.scale_6, + spiral=spiral, + to_shrink=self.labels, + target_p_spiral_width=15, + run_time=6, + ) + self.wait() + + def rescale_labels(self, labels): + for i, label in zip(it.count(1), labels): + height = label.get_height() + label.set_height( + 3 * height / (i**0.25), + about_point=label.get_bottom() + 0.5 * label.get_height() * DOWN, + ) + + +class PrimeSpiralsAtScale1000(SpiralScene): + def construct(self): + spiral = self.get_prime_p_spiral(10000) + self.add(spiral) + self.set_scale( + scale=1000, + spiral=spiral, + target_p_spiral_width=15, + run_time=0, + ) + + +class SeparateIntoTwoQuestions(Scene): + def construct(self): + top_q = TextMobject("Why do", " primes", " cause", " spirals", "?") + top_q.scale(2) + top_q.to_edge(UP) + + q1 = TextMobject("Where do the\\\\", "spirals", " come from?") + q2 = TextMobject("What happens when\\\\", "filtering to", " primes", "?") + for q in q1, q2: + q.scale(1.3) + q.next_to(top_q, DOWN, LARGE_BUFF) + q1.to_edge(LEFT) + q1.set_color(YELLOW) + q2.to_edge(RIGHT) + q2.set_color(TEAL) + + v_line = DashedLine( + top_q.get_bottom() + MED_SMALL_BUFF * DOWN, + FRAME_HEIGHT * DOWN / 2, + ) + + self.add(top_q) + self.wait() + + for q, text in [(q1, "spirals"), (q2, "primes")]: + self.play( + top_q.get_part_by_tex(text).set_color, q.get_color(), + TransformFromCopy( + top_q.get_part_by_tex(text), + q.get_part_by_tex(text), + ), + LaggedStartMap( + FadeIn, + filter( + lambda m: m is not q.get_part_by_tex(text), + q, + ) + ), + ) + self.wait() + self.play(ShowCreation(v_line)) + self.wait() + + +class TopQuestionCross(Scene): + def construct(self): + top_q = TextMobject("Why do", " primes", " cause", " spirals", "?") + top_q.scale(2) + top_q.to_edge(UP) + cross = Cross(top_q) + + self.play(ShowCreation(cross)) + self.wait() + + +class ExplainSixSpirals(ShowSpiralsForWholeNumbers): + CONFIG = { + "max_N": 150, + } + + def construct(self): + self.add_spirals_and_labels() + self.comment_on_arms() + self.talk_though_multiples_of_six() + self.limit_to_primes() + + def add_spirals_and_labels(self): + max_N = self.max_N + + spiral = self.get_v_spiral(range(max_N)) + primes = generate_prime_list(max_N) + spiral.set_color(YELLOW) + for n, box in enumerate(spiral): + if n in primes: + box.set_color(TEAL) + + labels = self.get_labels(range(max_N)) + + self.add(spiral, labels) + self.set_scale( + spiral=spiral, + scale=self.scale_6, + to_shrink=labels, + min_box_width=0.08, + run_time=0, + ) + self.rescale_labels(labels) + + self.spiral = spiral + self.labels = labels + + def comment_on_arms(self): + labels = self.labels + spiral = self.spiral + + label_groups = VGroup(*[labels[k::6] for k in range(6)]) + spiral_groups = VGroup(*[spiral[k::6] for k in range(6)]) + six_groups = VGroup(*[ + VGroup(sg, lg) + for sg, lg in zip(spiral_groups, label_groups) + ]) + rect_groups = VGroup(*[ + VGroup(*[ + SurroundingRectangle(label, stroke_width=2, buff=0.05) + for label in group + ]) + for group in label_groups + ]) + + formula = VGroup( + *TexMobject("6k", "+"), + Integer(1) + ) + formula.arrange(RIGHT, buff=SMALL_BUFF) + formula.scale(2) + formula.set_color(YELLOW) + formula.to_corner(UL) + formula_rect = SurroundingRectangle(formula, buff=MED_LARGE_BUFF - SMALL_BUFF) + formula_rect.set_fill(DARK_GREY, opacity=1) + formula_rect.set_stroke(WHITE, 1) + + # 6k + self.add(six_groups, formula_rect) + self.play( + LaggedStartMap(ShowCreation, rect_groups[0]), + FadeInFromDown(formula_rect), + FadeInFromDown(formula[0]), + *[ + ApplyMethod(group.set_opacity, 0.25) + for group in six_groups[1:] + ], + run_time=2 + ) + self.play( + LaggedStartMap( + FadeOut, rect_groups[0], + run_time=1, + ), + ) + self.wait() + + # 6k + 1 + self.play( + six_groups[0].set_opacity, 0.25, + six_groups[1].set_opacity, 1, + FadeIn(formula[1:]), + ) + self.wait(2) + + # 6k + m + for m in [2, 3, 4, 5]: + self.play( + six_groups[m - 1].set_opacity, 0.25, + six_groups[m].set_opacity, 1, + ChangeDecimalToValue(formula[2], m), + ) + self.wait() + self.play( + six_groups[5].set_opacity, 0.25, + six_groups[0].set_opacity, 1, + formula[1:].set_opacity, 0, + ) + self.wait() + + self.six_groups = six_groups + self.formula = VGroup(formula_rect, *formula) + + def talk_though_multiples_of_six(self): + spiral = self.spiral + labels = self.labels + formula = self.formula + + # Zoom in + self.add(spiral, labels, formula) + self.set_scale( + 4.5, + spiral=spiral, + to_shrink=labels, + run_time=2, + ) + self.wait() + + boxes = VGroup(*[ + VGroup(b.copy(), l.copy()) + for b, l in zip(spiral, labels) + ]) + boxes.set_opacity(1) + + lines = VGroup(*[ + Line(ORIGIN, box[0].get_center()) + for box in boxes + ]) + lines.set_stroke(LIGHT_GREY, width=2) + + arcs = self.get_arcs(range(31)) + + trash = VGroup() + + def show_steps(start, stop, added_anims=None, run_time=2): + if added_anims is None: + added_anims = [] + self.play( + *[ + ShowSubmobjectsOneByOne(group[start:stop + 1]) + for group in [arcs, boxes, lines] + ], + *added_anims, + rate_func=linear, + run_time=run_time, + ) + self.add_count_clicks(N=6, time=run_time) + trash.add(VGroup(arcs[stop], boxes[stop], lines[stop])) + + # Writing next to the 6 + six = boxes[6][1] + rhs = TexMobject( + "\\text{radians}", + "\\approx", + "2\\pi", + "\\text{ radians}" + ) + rhs.next_to(six, RIGHT, 2 * SMALL_BUFF, aligned_edge=DOWN) + rhs.add_background_rectangle() + tau_value = TexMobject("{:.8}\\dots".format(TAU)) + tau_value.next_to(rhs[3], UP, aligned_edge=LEFT) + + # Animations + show_steps(0, 6, run_time=3) + self.wait() + self.play(FadeIn(rhs)) + self.wait() + self.play(FadeInFromDown(tau_value)) + self.wait(2) + + show_steps(6, 12) + self.wait() + + show_steps(12, 18) + self.wait() + + # Zoom out + frame = self.camera_frame + frame.add(formula) + show_steps(18, 24, added_anims=[frame.scale, 2.5]) + self.wait() + show_steps(24, 30) + self.wait(2) + + self.play( + FadeOut(trash), + FadeOut(rhs), + FadeOut(tau_value), + spiral.set_opacity, 1, + labels.set_opacity, 1, + formula[1].set_opacity, 0, + ) + + def limit_to_primes(self): + formula = self.formula + formula_rect, six_k, plus, m_sym = formula + spiral = self.spiral + labels = self.labels + six_groups = self.six_groups + frame = self.camera_frame + + boxes = VGroup(*[ + VGroup(b, l) + for b, l in zip(spiral, labels) + ]) + prime_numbers = read_in_primes(self.max_N) + primes = VGroup() + non_primes = VGroup() + for n, box in enumerate(boxes): + if n in prime_numbers: + primes.add(box) + else: + non_primes.add(box) + + prime_label = TextMobject("Primes") + prime_label.set_color(TEAL) + prime_label.match_width(VGroup(six_k, m_sym)) + prime_label.move_to(six_k, LEFT) + + # Show just primes + self.add(primes, non_primes, formula) + self.play( + FadeIn(prime_label), + non_primes.set_opacity, 0.25, + ) + frame.add(prime_label) + self.play( + frame.scale, 1.5, + run_time=2, + ) + self.wait(2) + + cross_groups = VGroup() + for group in six_groups: + group.save_state() + boxes, labels = group + cross_group = VGroup() + for label in labels: + cross_group.add(Cross(label)) + cross_groups.add(cross_group) + cross_groups.set_stroke(width=3) + cross_groups[2].remove(cross_groups[2][0]) + cross_groups[3].remove(cross_groups[3][0]) + + # Show multiples of 6 + for r in [0, 2, 4, 3]: + arm = six_groups[r] + crosses = cross_groups[r] + self.add(arm, frame) + + anims = [arm.set_opacity, 1] + if r == 0: + anims += [ + prime_label.set_opacity, 0, + six_k.set_opacity, 1 + ] + elif r == 2: + m_sym.set_value(2) + anims += [ + plus.set_opacity, 1, + m_sym.set_opacity, 1, + ] + else: + anims.append(ChangeDecimalToValue(m_sym, r)) + self.play(*anims) + self.add(*crosses, frame) + self.play( + LaggedStartMap(ShowCreation, crosses), + ) + self.wait() + + # Fade forbidden groups + to_fade = VGroup(*[ + VGroup(six_groups[r], cross_groups[r]) + for r in (0, 2, 3, 4) + ]) + self.add(to_fade, frame) + self.play( + to_fade.set_opacity, 0.25, + VGroup(six_k, plus, m_sym).set_opacity, 0, + prime_label.set_opacity, 1, + ) + self.wait() + + # + def arc_func(self, t): + r = 0.25 + 0.02 * t + return r * np.array([np.cos(t), np.sin(t), 0]) + + def get_arc(self, n): + if n == 0: + return VectorizedPoint() + return ParametricFunction( + self.arc_func, + t_min=0, + t_max=n, + step_size=0.1, + stroke_width=2, + stroke_color=PINK, + ) + + def get_arcs(self, sequence): + return VGroup(*map(self.get_arc, sequence)) + + +class IntroduceResidueClassTerminology(Scene): + def construct(self): + self.add_title() + self.add_sequences() + self.add_terms() + self.highlight_example() + self.simple_english() + + def add_title(self): + title = TextMobject("Overly-fancy ", "terminology") + title.scale(1.5) + title.to_edge(UP, buff=MED_SMALL_BUFF) + underline = Line().match_width(title) + underline.next_to(title, DOWN, SMALL_BUFF) + + pre_title = TextMobject("Terminology") + pre_title.replace(title, dim_to_match=1) + + self.play(FadeInFromDown(pre_title)) + self.wait() + title[0].set_color(BLUE) + underline.set_color(BLUE) + self.play( + ReplacementTransform(pre_title[0], title[1]), + FadeInFrom(title[0], RIGHT), + GrowFromCenter(underline) + ) + self.play( + title[0].set_color, WHITE, + underline.set_color, WHITE, + ) + self.wait() + + title.add(underline) + self.add(title) + + self.title = title + self.underline = underline + + def add_sequences(self): + sequences = VGroup() + n_terms = 7 + + for r in range(6): + sequence = VGroup(*[ + Integer(6 * k + r) + for k in range(n_terms) + ]) + sequence.arrange(RIGHT, buff=0.4) + sequences.add(sequence) + + sequences.arrange(DOWN, buff=0.7, aligned_edge=LEFT) + for sequence in sequences: + for s1, s2 in zip(sequence[:n_terms], sequences[-1]): + s1.align_to(s2, RIGHT) + commas = VGroup() + for num in sequence[:-1]: + comma = TextMobject(",") + comma.next_to(num.get_corner(DR), RIGHT, SMALL_BUFF) + commas.add(comma) + dots = TexMobject("\\dots") + dots.next_to(sequence.get_corner(DR), RIGHT, SMALL_BUFF) + sequence.numbers = VGroup(*sequence) + sequence.commas = commas + sequence.dots = dots + + sequence.add(*commas) + sequence.add(dots) + sequence.sort(lambda p: p[0]) + + labels = VGroup(*[ + TexMobject("6k + {}:".format(r)) + for r in range(6) + ]) + labels.set_color(YELLOW) + for label, sequence in zip(labels, sequences): + label.next_to(sequence, LEFT, MED_LARGE_BUFF) + + group = VGroup(sequences, labels) + group.to_edge(LEFT).to_edge(DOWN, buff=MED_LARGE_BUFF) + + self.add(labels) + self.play(LaggedStart(*[ + LaggedStartMap( + FadeInFrom, sequence, + lambda m: (m, LEFT), + ) + for sequence in sequences + ], lag_ratio=0.3)) + self.wait() + + self.sequences = sequences + self.sequence_labels = labels + + def add_terms(self): + sequences = self.sequences + + terms = TextMobject( + "``", "Residue\\\\", + "classes\\\\", + "mod ", "6''" + ) + terms.scale(1.5) + terms.set_color(YELLOW) + terms.to_edge(RIGHT) + + res_brace = Brace(terms.get_part_by_tex("Residue"), UP) + remainder = TextMobject("Remainder") + remainder.next_to(res_brace, UP, SMALL_BUFF) + + mod_brace = Brace(terms.get_part_by_tex("mod"), DOWN) + mod_def = TextMobject( + "``where the thing\\\\you divide by is''" + ) + mod_def.next_to(mod_brace, DOWN, SMALL_BUFF) + + arrows = VGroup(*[ + Arrow(terms.get_left(), sequence.get_right()) + for sequence in sequences + ]) + arrows.set_color(YELLOW) + + self.play( + FadeIn(terms), + LaggedStartMap(ShowCreation, arrows), + ) + self.wait() + self.play( + GrowFromCenter(res_brace), + FadeInFromDown(remainder), + ) + self.wait() + self.play(GrowFromCenter(mod_brace)) + self.play(Write(mod_def)) + self.wait() + + self.terminology = VGroup( + terms, + res_brace, remainder, + mod_brace, mod_def, + arrows + ) + + def highlight_example(self): + sequences = self.sequences + labels = self.sequence_labels + + r = 2 + k = 3 + sequence = sequences[r] + label = labels[r] + r_tex = label[0][3] + n_rects = VGroup(*[ + SurroundingRectangle(num) + for num in sequence.numbers + ]) + r_rect = SurroundingRectangle(r_tex) + n_rects.set_color(RED) + r_rect.set_color(RED) + + n_rect = n_rects.submobjects.pop(k) + + self.play(ShowCreation(n_rect)) + self.wait() + self.play( + TransformFromCopy(n_rect, r_rect, path_arc=30 * DEGREES) + ) + self.wait() + self.play(ShowCreation(n_rects)) + self.wait() + self.play( + ShowCreationThenFadeOut( + self.underline.copy().set_color(PINK), + ) + ) + + def simple_english(self): + terminology = self.terminology + sequences = self.sequences + + randy = Randolph() + randy.set_height(2) + randy.flip() + randy.to_corner(DR) + + new_phrase = TextMobject("Everything 2 above\\\\a multiple of 6") + new_phrase.scale(1.2) + new_phrase.set_color(RED_B) + new_phrase.next_to(sequences[2]) + + self.play( + FadeOut(terminology), + FadeIn(new_phrase), + VFadeIn(randy), + randy.change, "sassy" + ) + self.wait() + self.play(randy.change, "angry") + for x in range(2): + self.play(Blink(randy)) + self.wait() + self.play( + FadeOut(new_phrase), + FadeIn(terminology), + FadeOutAndShift(randy, DOWN) + ) + self.wait() + self.wait(6) + + +class SimpleLongDivision(MovingCameraScene): + CONFIG = { + "camera_config": { + "background_color": DARKER_GREY + } + } + + def construct(self): + divisor = Integer(6) + num = Integer(20) + quotient = Integer(num.get_value() // divisor.get_value()) + to_subtract = Integer(-1 * quotient.get_value() * divisor.get_value()) + remainder = Integer(num.get_value() + to_subtract.get_value()) + + div_sym = VMobject() + div_sym.set_points_as_corners([0.2 * UP, UP, UP + 3 * RIGHT]) + + divisor.next_to(div_sym, LEFT, MED_SMALL_BUFF) + num.next_to(divisor, RIGHT, MED_LARGE_BUFF) + to_subtract.next_to(num, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) + h_line = Line(LEFT, RIGHT) + h_line.next_to(to_subtract, DOWN, buff=MED_SMALL_BUFF) + remainder.next_to(to_subtract, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) + quotient.next_to(num, UP, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) + + remainder_rect = SurroundingRectangle(remainder) + remainder_rect.set_color(RED) + + frame = self.camera_frame + frame.scale(0.7, about_point=ORIGIN) + + divisor.set_color(YELLOW) + num.set_color(RED) + + self.add(divisor) + self.add(div_sym) + self.add(num) + self.play(FadeInFromDown(quotient)) + self.play( + TransformFromCopy(divisor, to_subtract.copy()), + TransformFromCopy(quotient, to_subtract), + ) + self.play(ShowCreation(h_line)) + self.play(Write(remainder)) + self.play(ShowCreation(remainder_rect)) + self.wait() + self.play(FadeOut(remainder_rect)) + + +class ZoomOutWords(Scene): + def construct(self): + words = TextMobject("Zoom out!") + words.scale(3) + self.play(FadeInFromLarge(words)) + self.wait() + + +class Explain44Spirals(ExplainSixSpirals): + CONFIG = { + "max_N": 3000, + "initial_scale": 10, + "zoom_factor_1": 7, + "zoom_factor_2": 3, + "n_labels": 80, + } + + def construct(self): + self.add_spirals_and_labels() + self.show_44_steps() + self.show_top_right_arithmetic() + self.show_pi_approx_arithmetic() + self.count_by_44() + + def add_spirals_and_labels(self): + max_N = self.max_N + + wholes = self.get_p_spiral(range(max_N)) + primes = self.get_prime_p_spiral(max_N) + wholes.set_color(YELLOW) + spiral = PGroup(wholes, primes) + + labels = self.get_labels(range(self.n_labels)) + + self.add(spiral, labels) + self.set_scale( + spiral=spiral, + scale=self.initial_scale, + to_shrink=labels, + target_p_spiral_width=10, + run_time=0, + ) + self.rescale_labels(labels) + + self.spiral = spiral + self.labels = labels + + def show_44_steps(self): + labels = self.labels + + ns = range(45) + points = [self.get_polar_point(n, n) for n in ns] + lines = VGroup(*[ + Line(ORIGIN, point) + for point in points + ]) + lines.set_stroke(WHITE, 2) + arcs = self.get_arcs(ns) + + opaque_labels = labels.copy() + labels.set_opacity(0.25) + + trash = VGroup() + + def show_steps(start, stop, added_anims=None, run_time=2): + if added_anims is None: + added_anims = [] + + def rate_func(t): + return smooth(t, 2) + + self.play( + *[ + ShowSubmobjectsOneByOne(group[start:stop + 1]) + for group in [arcs, opaque_labels, lines] + ], + *added_anims, + rate_func=rate_func, + run_time=run_time, + ) + self.add_count_clicks( + N=(stop - start), time=run_time, + rate_func=rate_func + ) + trash.add(arcs[stop], opaque_labels[stop], lines[stop]) + + show_steps(0, 6) + self.wait() + show_steps(6, 44, added_anims=[FadeOut(trash)], run_time=4) + self.wait() + + self.spiral_group = trash[-3:] + + def show_top_right_arithmetic(self): + labels = self.labels + ff = labels[44].copy() + ff.generate_target() + + radians = TextMobject("radians") + ff.target.scale(1.5) + ff.target.set_opacity(1) + + unit_conversion = TexMobject( + "/\\,", "\\left(", "2\\pi", + "{\\text{radians}", "\\over", "\\text{rotations}}", + "\\right)" + ) + unit_conversion[1:].scale(0.7, about_edge=LEFT) + + top_line = VGroup(ff.target, radians, unit_conversion) + top_line.arrange(RIGHT) + ff.target.align_to(radians, DOWN) + top_line.to_corner(UR, buff=0.4) + + next_line = TexMobject( + "=", "44", "/", "2\\pi", + "\\text{ rotations}" + ) + next_line.next_to(top_line, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT) + + brace = Brace(next_line[1:4], DOWN, buff=SMALL_BUFF) + value = DecimalNumber(44 / TAU, num_decimal_places=8, show_ellipsis=True) + value.next_to(brace, DOWN) + + rect = SurroundingRectangle(VGroup(top_line, value), buff=MED_SMALL_BUFF) + rect.set_stroke(WHITE, 2) + rect.set_fill(DARKER_GREY, 0.9) + + self.play(MoveToTarget(ff)) + top_line.add(ff) + self.play(FadeInFrom(radians, LEFT)) + self.wait() + self.add(rect, top_line, unit_conversion) + self.play( + FadeIn(rect), + FadeIn(unit_conversion), + ) + self.wait() + self.play( + TransformFromCopy(ff, next_line.get_part_by_tex("44")), + FadeIn(next_line.get_part_by_tex("=")), + TransformFromCopy( + unit_conversion.get_part_by_tex("/"), + next_line.get_part_by_tex("/"), + ), + TransformFromCopy( + unit_conversion.get_part_by_tex("rotations"), + next_line.get_part_by_tex("rotations"), + ), + TransformFromCopy( + unit_conversion.get_part_by_tex("2\\pi"), + next_line.get_part_by_tex("2\\pi"), + ), + ) + self.wait() + self.play( + GrowFromCenter(brace), + Write(value), + ) + self.wait() + + self.right_arithmetic = VGroup( + rect, top_line, next_line, + brace, value + ) + + def show_pi_approx_arithmetic(self): + ra = self.right_arithmetic + ra_rect, ra_l1, ra_l2, ra_brace, ra_value = ra + + lines = VGroup( + TexMobject("{44", "\\over", "2\\pi}", "\\approx", "7"), + TexMobject("\\Leftrightarrow"), + TexMobject("{44", "\\over", "7}", "\\approx", "2\\pi"), + TexMobject("{22", "\\over", "7}", "\\approx", "\\pi"), + ) + lines.arrange(RIGHT, buff=MED_SMALL_BUFF) + lines.to_corner(UL) + lines[3].move_to(lines[2], LEFT) + + rect = SurroundingRectangle(lines, buff=MED_LARGE_BUFF) + rect.match_style(ra_rect) + + self.play( + FadeIn(rect), + LaggedStart( + TransformFromCopy(ra_l2[1:4], lines[0][:3]), + FadeIn(lines[0].get_part_by_tex("approx")), + TransformFromCopy(ra_value[0], lines[0].get_part_by_tex("7")), + run_time=2, + ) + ) + self.wait() + l0_copy = lines[0].copy() + self.play( + l0_copy.move_to, lines[2], + Write(lines[1]), + ) + self.play( + LaggedStart(*[ + ReplacementTransform( + l0_copy.get_part_by_tex(tex), + lines[2].get_part_by_tex(tex), + path_arc=60 * DEGREES, + ) + for tex in ["44", "\\over", "7", "approx", "2\\pi"] + ], lag_ratio=0.1, run_time=2), + ) + self.wait() + self.play(Transform(lines[2], lines[3])) + self.wait() + + left_arithmetic = VGroup(rect, lines[:3]) + self.play( + LaggedStart( + FadeOut(self.spiral_group[0]), + FadeOut(left_arithmetic), + FadeOut(self.right_arithmetic), + ) + ) + + def count_by_44(self): + ff_label, ff_line = self.spiral_group[1:] + faded_labels = self.labels + frame = self.camera_frame + + n_values = 100 + mod = 44 + values = range(mod, n_values * mod, mod) + points = [ + self.get_polar_point(n, n) + for n in values + ] + + p2l_tracker = ValueTracker( + get_norm(ff_label.get_bottom() - points[0]) + ) + get_p2l = p2l_tracker.get_value + l_height_ratio_tracker = ValueTracker( + ff_label.get_height() / frame.get_height() + ) + get_l_height_ratio = l_height_ratio_tracker.get_value + + n_labels = 10 + labels = VGroup(*[Integer(n) for n in values[:n_labels]]) + for label, point in zip(labels, points): + label.point = point + label.add_updater( + lambda l: l.set_height( + frame.get_height() * get_l_height_ratio() + ) + ) + label.add_updater( + lambda l: l.move_to( + l.point + get_p2l() * UP, + DOWN, + ) + ) + labels.set_stroke(BLACK, 2, background=True) + + lines = VGroup(ff_line) + for p1, p2 in zip(points, points[1:]): + lines.add(Line(p1, p2)) + lines.match_style(ff_line) + + self.remove(self.spiral_group) + self.remove(faded_labels[44]) + self.play( + frame.scale, self.zoom_factor_1, + p2l_tracker.set_value, 1, + l_height_ratio_tracker.set_value, 0.025, + FadeOut( + ff_label, + rate_func=squish_rate_func(smooth, 0, 1 / 8), + ), + LaggedStart( + *2 * [Animation(Group())], # Weird and dumb + *map(FadeIn, labels), + lag_ratio=0.5, + ), + LaggedStart( + *2 * [Animation(Group())], + *map(ShowCreation, lines[:len(labels)]), + lag_ratio=1, + ), + run_time=8, + ) + self.play( + frame.scale, self.zoom_factor_2, + l_height_ratio_tracker.set_value, 0.01, + ShowCreation(lines[len(labels):]), + run_time=8, + ) + + self.ff_spiral_lines = lines + self.ff_spiral_labels = labels + + # + def arc_func(self, t): + r = 0.1 * t + return r * np.array([np.cos(t), np.sin(t), 0]) + + +class Label44Spirals(Explain44Spirals): + def construct(self): + self.setup_spirals() + self.enumerate_spirals() + + def setup_spirals(self): + max_N = self.max_N + mod = 44 + primes = read_in_primes(max_N) + spirals = VGroup() + for r in range(mod): + ns = range(r, max_N, mod) + spiral = self.get_v_spiral(ns, box_width=1) + for box, n in zip(spiral, ns): + box.n = n + if n in primes: + box.set_color(TEAL) + else: + box.set_color(YELLOW) + spirals.add(spiral) + + self.add(spirals) + scale = np.prod([ + self.initial_scale, + self.zoom_factor_1, + self.zoom_factor_2, + ]) + self.set_scale( + spiral=VGroup(*it.chain(*spirals)), + scale=scale, + run_time=0 + ) + + self.spirals = spirals + + def enumerate_spirals(self): + spirals = self.spirals + labels = self.get_spiral_arm_labels(spirals) + + self.play( + spirals[1:].set_opacity, 0.25, + FadeIn(labels[0]), + ) + self.wait() + + for n in range(10): + arc = Arc( + start_angle=n + 0.2, + angle=0.9, + radius=1.5, + ) + arc.add_tip() + mid_point = arc.point_from_proportion(0.5) + r_label = TextMobject("1 radian") + # r_label.rotate( + # angle_of_vector(arc.get_end() - arc.get_start()) - PI + # ) + r_label.next_to(mid_point, normalize(mid_point)) + if n > 2: + r_label.set_opacity(0) + + self.play( + ShowCreation(arc), + FadeIn(r_label), + spirals[n + 1].set_opacity, 1, + TransformFromCopy(labels[n], labels[n + 1]) + ) + self.play( + FadeOut(arc), + FadeOut(r_label), + spirals[n].set_opacity, 0.25, + FadeOut(labels[n]), + ) + + # + def get_spiral_arm_labels(self, spirals, index=15): + mod = 44 + labels = VGroup(*[ + VGroup( + *TexMobject("44k", "+"), + Integer(n) + ).arrange(RIGHT, buff=SMALL_BUFF) + for n in range(mod) + ]) + labels[0][1:].set_opacity(0) + labels.scale(1.5) + labels.set_color(YELLOW) + + for label, spiral in zip(labels, spirals): + box = spiral[index] + vect = rotate_vector(box.get_center(), 90 * DEGREES) + label.next_to(box, normalize(vect), SMALL_BUFF) + labels[0].shift(UR + 1.25 * RIGHT) + return labels + + +class ResidueClassMod44Label(Scene): + def construct(self): + text = TextMobject( + "``Residue class mod 44''" + ) + text.scale(2) + text.to_corner(UL) + + self.play(Write(text)) + self.wait() + + +class EliminateNonPrimativeResidueClassesOf44(Label44Spirals): + CONFIG = { + "max_N": 7000, + } + + def construct(self): + self.setup_spirals() + self.eliminate_classes() + self.zoom_out() + self.filter_to_primes() + + def eliminate_classes(self): + spirals = self.spirals + labels = self.get_spiral_arm_labels(spirals) + + # Eliminate factors of 2 + self.play( + spirals[1:].set_opacity, 0.5, + FadeIn(labels[0]), + ) + self.wait() + self.play( + FadeOut(spirals[0]), + FadeOut(labels[0]), + ) + for n in range(2, 8, 2): + self.play( + FadeIn(labels[n]), + spirals[n].set_opacity, 1, + ) + self.play(FadeOut(VGroup(labels[n], spirals[n]))) + + words = TextMobject("All even numbers") + words.scale(1.5) + words.to_corner(UL) + self.play( + LaggedStart(*[ + ApplyMethod(spiral.set_opacity, 1) + for spiral in spirals[8::2] + ], lag_ratio=0.01), + FadeIn(words), + ) + self.play( + FadeOut(words), + FadeOut(spirals[8::2]) + ) + self.wait() + + # Eliminate factors of 11 + for k in [11, 33]: + self.play( + spirals[k].set_opacity, 1, + FadeIn(labels[k]) + ) + self.wait() + self.play( + FadeOut(spirals[k]), + FadeOut(labels[k]), + ) + + admissible_spirals = VGroup(*[ + spiral + for n, spiral in enumerate(spirals) + if n % 2 != 0 and n % 11 != 0 + + ]) + + self.play(admissible_spirals.set_opacity, 1) + + self.admissible_spirals = admissible_spirals + + def zoom_out(self): + frame = self.camera_frame + admissible_spirals = self.admissible_spirals + admissible_spirals.generate_target() + for spiral in admissible_spirals.target: + for box in spiral: + box.scale(3) + + self.play( + frame.scale, 4, + MoveToTarget(admissible_spirals), + run_time=3, + ) + + def filter_to_primes(self): + admissible_spirals = self.admissible_spirals + frame = self.camera_frame + primes = read_in_primes(self.max_N) + + to_fade = VGroup(*[ + box + for spiral in admissible_spirals + for box in spiral + if box.n not in primes + ]) + words = TextMobject("Just the primes") + words.set_height(0.1 * frame.get_height()) + words.next_to(frame.get_corner(UL), DR, LARGE_BUFF) + + self.play( + LaggedStartMap(FadeOut, to_fade, lag_ratio=2 / len(to_fade)), + FadeIn(words), + ) + self.wait() + + +class IntroduceTotientJargon(TeacherStudentsScene): + def construct(self): + self.add_title() + self.eliminate_non_coprimes() + + def add_title(self): + self.teacher_says( + "More jargon!", + target_mode="hooray", + ) + self.change_all_student_modes("erm") + words = self.teacher.bubble.content + + words.generate_target() + words.target.scale(1.5) + words.target.center().to_edge(UP, buff=MED_SMALL_BUFF) + words.target.set_color(BLUE) + underline = Line(LEFT, RIGHT) + underline.match_width(words.target) + underline.next_to(words.target, DOWN, SMALL_BUFF) + underline.scale(1.2) + + self.play( + MoveToTarget(words), + FadeOut(self.teacher.bubble), + LaggedStart(*[ + FadeOutAndShift(pi, 4 * DOWN) + for pi in self.pi_creatures + ]), + ShowCreation(underline) + ) + + def eliminate_non_coprimes(self): + number_grid = VGroup(*[ + VGroup(*[ + Integer(n) for n in range(11 * k, 11 * (k + 1)) + ]).arrange(DOWN) + for k in range(4) + ]).arrange(RIGHT, buff=1) + numbers = VGroup(*it.chain(*number_grid)) + numbers.set_height(6) + numbers.move_to(4 * LEFT) + numbers.to_edge(DOWN) + + evens = VGroup(*filter( + lambda nm: nm.get_value() % 2 == 0, + numbers + )) + div11 = VGroup(*filter( + lambda nm: nm.get_value() % 11 == 0, + numbers + )) + coprimes = VGroup(*filter( + lambda nm: nm not in evens and nm not in div11, + numbers + )) + + words = TextMobject( + "Which ones ", "don't\\\\", + "share any factors\\\\", + "with ", "44", + alignment="" + ) + words.scale(1.5) + words.next_to(ORIGIN, RIGHT) + + ff = words.get_part_by_tex("44") + ff.set_color(YELLOW) + ff.generate_target() + + # Show coprimes + self.play( + ShowIncreasingSubsets(numbers, run_time=3), + FadeInFrom(words, LEFT) + ) + self.wait() + for group in evens, div11: + rects = VGroup(*[ + SurroundingRectangle(number, color=RED) + for number in group + ]) + self.play(LaggedStartMap(ShowCreation, rects, run_time=1)) + self.play( + LaggedStart(*[ + ApplyMethod(number.set_opacity, 0.2) + for number in group + ]), + LaggedStartMap(FadeOut, rects), + run_time=1 + ) + self.wait() + + # Rearrange words + dsf = words[1:3] + dsf.generate_target() + dsf.target.arrange(RIGHT) + dsf.target[0].align_to(dsf.target[1][0], DOWN) + + example = numbers[35].copy() + example.generate_target() + example.target.match_height(ff) + num_pair = VGroup( + ff.target, + TextMobject("and").scale(1.5), + example.target, + ) + num_pair.arrange(RIGHT) + num_pair.move_to(words.get_top(), DOWN) + dsf.target.next_to(num_pair, DOWN, MED_LARGE_BUFF) + + phrase1 = TextMobject("are ", "``relatively prime''") + phrase2 = TextMobject("are ", "``coprime''") + for phrase in phrase1, phrase2: + phrase.scale(1.5) + phrase.move_to(dsf.target) + phrase[1].set_color(BLUE) + phrase.arrow = TexMobject("\\Updownarrow") + phrase.arrow.scale(1.5) + phrase.arrow.next_to(phrase, DOWN, 2 * SMALL_BUFF) + phrase.rect = SurroundingRectangle(phrase[1]) + phrase.rect.set_stroke(BLUE) + + self.play( + FadeOut(words[0]), + FadeOut(words[3]), + MoveToTarget(dsf), + MoveToTarget(ff), + GrowFromCenter(num_pair[1]), + ) + self.play( + MoveToTarget(example, path_arc=30 * DEGREES), + ) + self.wait() + self.play( + dsf.next_to, phrase1.arrow, DOWN, SMALL_BUFF, + GrowFromEdge(phrase1.arrow, UP), + GrowFromCenter(phrase1), + ShowCreation(phrase1.rect) + ) + self.play(FadeOut(phrase1.rect)) + self.wait() + self.play( + VGroup(dsf, phrase1, phrase1.arrow).next_to, + phrase2.arrow, DOWN, SMALL_BUFF, + GrowFromEdge(phrase2.arrow, UP), + GrowFromCenter(phrase2), + ShowCreation(phrase2.rect) + ) + self.play(FadeOut(phrase2.rect)) + self.wait() + + # Count through coprimes + coprime_rects = VGroup(*map(SurroundingRectangle, coprimes)) + coprime_rects.set_stroke(BLUE, 2) + example_anim = UpdateFromFunc( + example, lambda m: m.set_value(coprimes[len(coprime_rects) - 1].get_value()) + ) + self.play( + ShowIncreasingSubsets(coprime_rects, int_func=np.ceil), + example_anim, + run_time=3, + rate_func=linear, + ) + self.wait() + + # Show totient function + words_to_keep = VGroup(ff, num_pair[1], example, phrase2) + to_fade = VGroup(phrase2.arrow, phrase1, phrase1.arrow, dsf) + + totient = TexMobject("\\phi", "(", "44", ")", "=", "20") + totient.set_color_by_tex("44", YELLOW) + totient.scale(1.5) + totient.move_to(num_pair, UP) + phi = totient.get_part_by_tex("phi") + rhs = Integer(20) + rhs.replace(totient[-1], dim_to_match=1) + totient.submobjects[-1] = rhs + + self.play( + words_to_keep.to_edge, DOWN, + MaintainPositionRelativeTo(to_fade, words_to_keep), + VFadeOut(to_fade), + ) + self.play(FadeIn(totient)) + self.wait() + + # Label totient + brace = Brace(phi, DOWN) + etf = TextMobject("Euler's totient function") + etf.next_to(brace, DOWN) + etf.shift(RIGHT) + + self.play( + GrowFromCenter(brace), + FadeInFrom(etf, UP) + ) + self.wait() + self.play( + ShowIncreasingSubsets(coprime_rects), + UpdateFromFunc( + rhs, lambda m: m.set_value(len(coprime_rects)), + ), + example_anim, + rate_func=linear, + run_time=3, + ) + self.wait() + + # Show totatives + totient_group = VGroup(totient, brace, etf) + for cp, rect in zip(coprimes, coprime_rects): + cp.add(rect) + + self.play( + coprimes.arrange, RIGHT, {"buff": SMALL_BUFF}, + coprimes.set_width, FRAME_WIDTH - 1, + coprimes.move_to, 2 * UP, + FadeOut(evens), + FadeOut(div11[1::2]), + FadeOutAndShiftDown(words_to_keep), + totient_group.center, + totient_group.to_edge, DOWN, + ) + + totatives = TextMobject("``Totatives''") + totatives.scale(2) + totatives.set_color(BLUE) + totatives.move_to(ORIGIN) + arrows = VGroup(*[ + Arrow(totatives.get_top(), coprime.get_bottom()) + for coprime in coprimes + ]) + arrows.set_color(WHITE) + + self.play( + FadeIn(totatives), + LaggedStartMap(VFadeInThenOut, arrows, run_time=4, lag_ratio=0.05) + ) + self.wait(2) + + +class TwoUnrelatedFacts(Scene): + def construct(self): + self.add_title() + self.show_columns() + + def add_title(self): + title = TextMobject("Two (unrelated) bits of number theory") + title.set_width(FRAME_WIDTH - 1) + title.to_edge(UP) + h_line = Line() + h_line.match_width(title) + h_line.next_to(title, DOWN, SMALL_BUFF) + h_line.set_stroke(LIGHT_GREY) + + self.play( + FadeIn(title), + ShowCreation(h_line), + ) + + self.h_line = h_line + + def show_columns(self): + h_line = self.h_line + v_line = Line( + h_line.get_center() + MED_SMALL_BUFF * DOWN, + FRAME_HEIGHT * DOWN / 2, + ) + v_line.match_style(h_line) + + approx = TexMobject( + "{44 \\over 7} \\approx 2\\pi" + ) + approx.scale(1.5) + approx.next_to( + h_line.point_from_proportion(0.25), + DOWN, MED_LARGE_BUFF, + ) + + mod = 44 + n_terms = 9 + residue_classes = VGroup() + prime_numbers = read_in_primes(1000) + primes = VGroup() + non_primes = VGroup() + for r in range(mod): + if r <= 11 or r == 43: + row = VGroup() + for n in range(r, r + n_terms * mod, mod): + elem = Integer(n) + comma = TexMobject(",") + comma.next_to( + elem.get_corner(DR), + RIGHT, SMALL_BUFF + ) + elem.add(comma) + row.add(elem) + if n in prime_numbers: + primes.add(elem) + else: + non_primes.add(elem) + row.arrange(RIGHT, buff=0.3) + dots = TexMobject("\\dots") + dots.next_to(row.get_corner(DR), RIGHT, SMALL_BUFF) + dots.shift(SMALL_BUFF * UP) + row.add(dots) + row.r = r + if r == 12: + row = TexMobject("\\vdots") + residue_classes.add(row) + + residue_classes.arrange(DOWN) + residue_classes[-2].align_to(residue_classes, LEFT) + residue_classes[-2].shift(MED_SMALL_BUFF * RIGHT) + residue_classes.set_height(6) + residue_classes.next_to(ORIGIN, RIGHT) + residue_classes.to_edge(DOWN, buff=MED_SMALL_BUFF) + + def get_line(row): + return Line( + row.get_left(), row.get_right(), + stroke_color=RED, + stroke_width=4, + ) + + even_lines = VGroup(*[ + get_line(row) + for row in residue_classes[:12:2] + ]) + eleven_line = get_line(residue_classes[11]) + eleven_line.set_color(PINK) + for line in [even_lines[1], eleven_line]: + line.scale(0.93, about_edge=RIGHT) + + self.play(ShowCreation(v_line)) + self.wait() + self.play(FadeInFrom(approx, DOWN)) + self.wait() + self.play(FadeIn(residue_classes)) + self.wait() + self.play( + LaggedStartMap(ShowCreation, even_lines), + ) + self.wait() + self.play(ShowCreation(eleven_line)) + self.wait() + self.play( + primes.set_color, TEAL, + non_primes.set_opacity, 0.25, + even_lines.set_opacity, 0.25, + eleven_line.set_opacity, 0.25, + ) + self.wait() + + +class ExplainRays(Explain44Spirals): + CONFIG = { + "max_N": int(5e5), + "axes_config": { + "x_min": -1000, + "x_max": 1000, + "y_min": -1000, + "y_max": 1000, + "axis_config": { + "tick_frequency": 50, + }, + }, + } + + def construct(self): + self.add_spirals_and_labels() + self.show_710th_point() + self.show_arithmetic() + self.zoom_and_count() + + def show_710th_point(self): + spiral = self.spiral + axes = self.axes + labels = self.labels + + scale_factor = 12 + + fade_rect = FullScreenFadeRectangle() + fade_rect.scale(scale_factor) + + new_ns = list(range(711)) + bright_boxes = self.get_v_spiral(new_ns) + bright_boxes.set_color(YELLOW) + for n, box in enumerate(bright_boxes): + box.set_height(0.02 * np.sqrt(n)) + + big_labels = self.get_labels(new_ns) + + index_tracker = ValueTracker(44) + + labeled_box = VGroup(Square(), Integer(0)) + + def update_labeled_box(mob): + index = int(index_tracker.get_value()) + labeled_box[0].become(bright_boxes[index]) + labeled_box[1].become(big_labels[index]) + + labeled_box.add_updater(update_labeled_box) + + self.set_scale( + scale=120, + spiral=spiral, + to_shrink=labels, + ) + + box_710 = self.get_v_spiral([710])[0] + box_710.scale(2) + box_710.set_color(YELLOW) + label_710 = Integer(710) + label_710.scale(1.5) + label_710.next_to(box_710, UP) + + arrow = Arrow( + ORIGIN, DOWN, + stroke_width=6, + max_tip_length_to_length_ratio=0.35, + max_stroke_width_to_length_ratio=10, + tip_length=0.35 + ) + arrow.match_color(box_710) + arrow.next_to(box_710, UP, SMALL_BUFF) + label_710.next_to(arrow, UP, SMALL_BUFF) + + self.add(spiral, fade_rect, axes, labels) + self.play( + FadeIn(fade_rect), + FadeOut(labels), + FadeInFromLarge(box_710), + FadeInFrom(label_710, DOWN), + ShowCreation(arrow), + ) + self.wait() + + self.fade_rect = fade_rect + self.box_710 = box_710 + self.label_710 = label_710 + self.arrow = arrow + + def show_arithmetic(self): + label_710 = self.label_710 + + equation = TexMobject( + "710", "\\text{ radians}", "=", + "(710 / 2\\pi)", "\\text{ rotations}", + ) + equation.to_corner(UL) + frac = equation.get_part_by_tex("710 / 2\\pi") + brace = Brace(frac, DOWN, buff=SMALL_BUFF) + value = TextMobject("{:.15}".format(710 / TAU)) + value.next_to(brace, DOWN, SMALL_BUFF) + values = VGroup(*[ + value[0][:n].deepcopy().next_to(brace, DOWN, SMALL_BUFF) + for n in [3, *range(5, 13)] + ]) + + group = VGroup(equation, brace, value) + rect = SurroundingRectangle(group, buff=MED_SMALL_BUFF) + rect.set_stroke(WHITE, 2) + rect.set_fill(DARK_GREY, 1) + + approx = TexMobject( + "{710", "\\over", "113}", + "\\approx", "2\\pi", + ) + approx.next_to(rect, DOWN) + approx.align_to(equation, LEFT) + + approx2 = TexMobject( + "{355", "\\over", "113}", + "\\approx", "\\pi", + ) + approx2.next_to(approx, RIGHT, LARGE_BUFF) + + self.play( + FadeIn(rect), + TransformFromCopy(label_710, equation[0]), + FadeIn(equation[1:3]), + ) + self.play( + FadeInFrom(equation[3:], LEFT) + ) + self.play(GrowFromCenter(brace)) + self.play( + ShowSubmobjectsOneByOne(values), + run_time=3, + rate_func=linear, + ) + self.wait() + self.play( + rect.stretch, 2, 1, {"about_edge": UP}, + LaggedStart( + TransformFromCopy( # 710 + equation[3][1:4], + approx[0], + ), + FadeIn(approx[1][0]), + TransformFromCopy( # 113 + values[-1][:3], + approx[2], + ), + FadeIn(approx[3]), + TransformFromCopy( # 2pi + equation[3][5:7], + approx[4], + ), + run_time=2, + ) + ) + self.wait() + self.play( + TransformFromCopy(approx, approx2), + ) + self.wait() + self.play( + FadeOut(VGroup( + rect, equation, brace, values[-1], + approx, approx2 + )), + self.fade_rect.set_opacity, 0.25, + ) + + def zoom_and_count(self): + label = self.label_710 + arrow = self.arrow + box = self.box_710 + axes = self.axes + spiral = self.spiral + + times = TexMobject("\\times") + times.next_to(label, LEFT, SMALL_BUFF) + k_label = Integer(1) + k_label.match_height(label) + k_label.set_color(YELLOW) + k_label.next_to(times, LEFT) + + boxes = VGroup(*[box.copy() for x in range(150)]) + box_height_tracker = ValueTracker(box.get_height()) + + def get_k(): + max_x = axes.x_axis.p2n(label.get_center()) + return max(1, int(max_x / 710)) + + def get_k_point(k): + return self.get_polar_point(710 * k, 710 * k) + + def update_arrow(arrow): + point = get_k_point(get_k()) + arrow.put_start_and_end_on( + label.get_bottom() + SMALL_BUFF * DOWN, + point + SMALL_BUFF * UP + ) + + def get_unit(): + return get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)) + + def update_boxes(boxes): + box_height = box_height_tracker.get_value() + for k, box in enumerate(boxes): + box.set_height(box_height) + box.move_to(get_k_point(k)) + + arrow.add_updater(update_arrow) + boxes.add_updater(update_boxes) + k_label.add_updater( + lambda d: d.set_value(get_k()).next_to( + times, LEFT, SMALL_BUFF + ) + ) + + self.remove(box) + self.add(times, k_label, boxes) + self.set_scale( + scale=10000, + spiral=self.spiral, + run_time=8, + target_p_spiral_width=2, + added_anims=[ + box_height_tracker.set_value, 0.035, + ] + ) + self.wait() + + # Show other residue classes + new_label = TexMobject( + "710", "k", "+", + tex_to_color_map={"k": YELLOW} + ) + new_label.match_height(label) + new_label.next_to(boxes, UP, SMALL_BUFF) + new_label.to_edge(RIGHT) + new_label[2].set_opacity(0) + + r_label = Integer(1) + r_label.match_height(new_label) + r_label.set_opacity(0) + r_label.add_updater( + lambda m: m.next_to(new_label, RIGHT, SMALL_BUFF) + ) + + k_label.clear_updaters() + self.play( + FadeOut(times), + ReplacementTransform(label, new_label[0]), + ReplacementTransform(k_label, new_label[1]), + FadeOut(arrow) + ) + + boxes.clear_updaters() + for r in range(1, 12): + if r in [3, 6]: + vect = UR + else: + vect = RIGHT + point = rotate_vector(boxes[40].get_center(), 1) + new_boxes = boxes.copy() + new_boxes.rotate(1, about_point=ORIGIN) + for box in new_boxes: + box.rotate(-1) + self.play( + FadeOut(boxes), + LaggedStartMap(FadeIn, new_boxes, lag_ratio=0.01), + new_label.set_opacity, 1, + new_label.next_to, point, vect, + r_label.set_opacity, 1, + ChangeDecimalToValue(r_label, r), + run_time=1, + ) + self.remove(boxes) + boxes = new_boxes + self.add(boxes) + self.wait() + + # Show just the primes + self.play( + FadeOut(boxes), + FadeOut(new_label), + FadeOut(r_label), + FadeOut(self.fade_rect) + ) + self.set_scale( + 30000, + spiral=spiral, + run_time=4, + ) + self.wait() + self.remove(spiral) + self.add(spiral[1]) + self.wait() + + +class CompareTauToApprox(Scene): + def construct(self): + eqs = VGroup( + TexMobject("2\\pi", "=", "{:.10}\\dots".format(TAU)), + TexMobject("\\frac{710}{113}", "=", "{:.10}\\dots".format(710 / 113)), + ) + eqs.arrange(DOWN, buff=LARGE_BUFF) + eqs[1].shift((eqs[0][2].get_left()[0] - eqs[1][2].get_left()[0]) * RIGHT) + + eqs.generate_target() + for eq in eqs.target: + eq[2][:8].set_color(RED) + eq.set_stroke(BLACK, 8, background=True) + + self.play(LaggedStart( + FadeInFrom(eqs[0], DOWN), + FadeInFrom(eqs[1], UP), + )) + self.play(MoveToTarget(eqs)) + self.wait() + + +class RecommendedMathologerVideo(Scene): + def construct(self): + full_rect = FullScreenFadeRectangle() + full_rect.set_fill(DARK_GREY, 1) + self.add(full_rect) + + title = TextMobject("Recommended Mathologer video") + title.set_width(FRAME_WIDTH - 1) + title.to_edge(UP) + screen_rect = SurroundingRectangle(ScreenRectangle(height=5.9), buff=SMALL_BUFF) + screen_rect.next_to(title, DOWN) + screen_rect.set_fill(BLACK, 1) + screen_rect.set_stroke(WHITE, 3) + + self.add(screen_rect) + self.play(Write(title)) + self.wait() + + +class ShowClassesOfPrimeRays(SpiralScene): + CONFIG = { + "max_N": int(1e6), + "scale": 1e5 + } + + def construct(self): + self.setup_rays() + self.show_classes() + + def setup_rays(self): + spiral = self.get_prime_p_spiral(self.max_N) + self.add(spiral) + self.set_scale( + scale=self.scale, + spiral=spiral, + target_p_spiral_width=3, + run_time=0 + ) + + def show_classes(self): + max_N = self.max_N + mod = 710 + primes = read_in_primes(max_N) + + rect = FullScreenFadeRectangle() + rect.set_opacity(0) + self.add(rect) + + last_ray = PMobject() + last_label = VGroup(*[VectorizedPoint() for x in range(3)]) + for i in range(40): + if get_gcd(i, mod) != 1: + continue + + r = (INV_113_MOD_710 * i) % mod + + sequence = filter( + lambda x: x in primes, + range(r, max_N, mod) + ) + ray = self.get_v_spiral(sequence, box_width=0.03) + ray.set_color(GREEN) + ray.set_opacity(0.9) + + label = VGroup( + *TexMobject("710k", "+"), + Integer(r) + ) + label.arrange(RIGHT, buff=SMALL_BUFF) + label.next_to(ray[100], UL, SMALL_BUFF) + + label[2].save_state() + label[2].set_opacity(0) + label[2].move_to(last_label, RIGHT) + + self.play( + rect.set_opacity, 0.5, + ShowCreation(ray), + LaggedStartMap(FadeOut, last_ray), + ReplacementTransform(last_label[:2], label[:2]), + Restore(label[2]), + last_label[2].move_to, label[2].saved_state, + last_label[2].set_opacity, 0, + ) + self.remove(last_label) + self.add(label) + self.wait() + + last_ray = ray + last_label = label + + +class ShowFactorsOf710(Scene): + def construct(self): + equation = TexMobject( + "710", "=", + "71", "\\cdot", + "5", "\\cdot", + "2", + ) + equation.scale(1.5) + equation.to_corner(UL) + ten = TexMobject("10") + ten.match_height(equation) + ten.move_to(equation.get_part_by_tex("5"), LEFT) + + self.add(equation[0]) + self.wait() + self.play( + TransformFromCopy( + equation[0][:2], + equation[2], + ), + FadeIn(equation[1]), + FadeIn(equation[3]), + TransformFromCopy( + equation[0][2:], + ten, + ) + ) + self.wait() + self.remove(ten) + self.play(*[ + TransformFromCopy(ten, mob) + for mob in equation[4:] + ]) + self.wait() + + # Circle factors + colors = [RED, BLUE, PINK] + for factor, color in zip(equation[:-6:-2], colors): + rect = SurroundingRectangle(factor) + rect.set_color(color) + self.play(ShowCreation(rect)) + self.wait() + self.play(FadeOut(rect)) + + +class LookAtRemainderMod710(Scene): + def construct(self): + t2c = { + "n": YELLOW, + "r": GREEN + } + equation = TexMobject( + "n", "=", "710", "k", "+", "r", + tex_to_color_map=t2c, + ) + equation.scale(1.5) + + n_arrow = Vector(UP).next_to(equation.get_part_by_tex("n"), DOWN) + r_arrow = Vector(UP).next_to(equation.get_part_by_tex("r"), DOWN) + n_arrow.set_color(t2c["n"]) + r_arrow.set_color(t2c["r"]) + + n_label = TextMobject("Some\\\\number") + r_label = TextMobject("Remainder") + VGroup(n_label, r_label).scale(1.5) + n_label.next_to(n_arrow, DOWN) + n_label.match_color(n_arrow) + r_label.next_to(r_arrow, DOWN) + r_label.match_color(r_arrow) + + self.add(equation) + self.play( + FadeInFrom(n_label, UP), + ShowCreation(n_arrow), + ) + self.wait() + self.play( + FadeInFrom(r_label, DOWN), + ShowCreation(r_arrow), + ) + self.wait() + + +class EliminateNonPrimative710Residues(ShowClassesOfPrimeRays): + CONFIG = { + "max_N": int(5e5), + "scale": 5e4, + } + + def construct(self): + self.setup_rays() + self.eliminate_classes() + + def setup_rays(self): + mod = 710 + rays = PGroup(*[ + self.get_p_spiral(range(r, self.max_N, mod)) + for r in range(mod) + ]) + rays.set_color(YELLOW) + self.add(rays) + self.set_scale( + scale=self.scale, + spiral=rays, + target_p_spiral_width=1, + run_time=0, + ) + + self.rays = rays + + def eliminate_classes(self): + rays = self.rays + rect = FullScreenFadeRectangle() + rect.set_opacity(0) + + for r, ray in enumerate(rays): + ray.r = r + + mod = 710 + odds = PGroup(*[rays[i] for i in range(1, mod, 2)]) + mult5 = PGroup(*[rays[i] for i in range(0, mod, 5) if i % 2 != 0]) + mult71 = PGroup(*[rays[i] for i in range(0, mod, 71) if (i % 2 != 0 and i % 5 != 0)]) + colors = [RED, BLUE, PINK] + + pre_label, r_label = label = VGroup( + TexMobject("710k + "), + Integer(100) + ) + label.scale(1.5) + label.arrange(RIGHT, buff=SMALL_BUFF) + label.set_stroke(BLACK, 5, background=True, family=True) + label.next_to(ORIGIN, DOWN) + + r_label.group = odds + r_label.add_updater( + lambda m: m.set_value(m.group[-1].r if len(m.group) > 0 else 1), + ) + + self.remove(rays) + # Odds + odds.set_stroke_width(3) + self.add(odds, label) + self.play( + ShowIncreasingSubsets(odds, int_func=np.ceil), + run_time=10, + ) + self.play(FadeOut(label)) + self.remove(odds) + self.add(*odds) + self.wait() + + # Multiples of 5 then 71 + for i, group in [(1, mult5), (2, mult71)]: + group_copy = group.copy() + group_copy.set_color(colors[i]) + group_copy.set_stroke_width(4) + r_label.group = group_copy + self.add(group_copy, label) + self.play( + ShowIncreasingSubsets(group_copy, int_func=np.ceil, run_time=10), + ) + self.play(FadeOut(label)) + self.wait() + self.remove(group_copy, *group) + self.wait() + + +class Show280Computation(Scene): + def construct(self): + equation = TexMobject( + "\\phi(710) = ", + "710", + "\\left({1 \\over 2}\\right)", + "\\left({4 \\over 5}\\right)", + "\\left({70 \\over 71}\\right)", + "=", + "280", + ) + equation.set_width(FRAME_WIDTH - 1) + equation.move_to(UP) + words = VGroup( + TextMobject("Filter out\\\\evens"), + TextMobject("Filter out\\\\multiples of 5"), + TextMobject("Filter out\\\\multiples of 71"), + ) + vects = [DOWN, UP, DOWN] + colors = [RED, BLUE, LIGHT_PINK] + for part, word, vect, color in zip(equation[2:5], words, vects, colors): + brace = Brace(part, vect) + brace.stretch(0.8, 0) + word.brace = brace + word.next_to(brace, vect) + part.set_color(color) + word.set_color(color) + word.set_stroke(BLACK, 5, background=True) + equation.set_stroke(BLACK, 5, background=True) + + etf_label = TextMobject("Euler's totient function") + etf_label.to_corner(UL) + arrow = Arrow(etf_label.get_bottom(), equation[0][0].get_top()) + equation[0][0].set_color(YELLOW) + etf_label.set_color(YELLOW) + + rect = FullScreenFadeRectangle(fill_opacity=0.9) + self.play( + FadeIn(rect), + FadeInFromDown(equation), + FadeIn(etf_label), + GrowArrow(arrow), + ) + self.wait() + for word in words: + self.play( + FadeIn(word), + GrowFromCenter(word.brace), + ) + self.wait() + + +class TeacherHoldUp(TeacherStudentsScene): + def construct(self): + self.change_all_student_modes( + "pondering", look_at_arg=2 * UP, + added_anims=[ + self.teacher.change, "raise_right_hand" + ] + ) + self.wait(8) + + +class DiscussPrimesMod10(Scene): + def construct(self): + labels = VGroup(*[ + TextMobject(str(n), " mod 10:") + for n in range(10) + ]) + labels.arrange(DOWN, buff=0.35, aligned_edge=LEFT) + labels.to_edge(LEFT) + # digits = VGroup(*[l[0] for l in labels]) + labels.set_submobject_colors_by_gradient(YELLOW, BLUE) + + sequences = VGroup(*[ + VGroup(*[ + Integer(n).shift((n // 10) * RIGHT) + for n in range(r, 100 + r, 10) + ]) + for r in range(10) + ]) + for sequence, label in zip(sequences, labels): + sequence.next_to(label, RIGHT, buff=MED_LARGE_BUFF) + for item in sequence: + if item is sequence[-1]: + punc = TexMobject("\\dots") + else: + punc = TextMobject(",") + punc.next_to(item.get_corner(DR), RIGHT, SMALL_BUFF) + item.add(punc) + + # Introduce everything + self.play(LaggedStart(*[ + FadeInFrom(label, UP) + for label in labels + ])) + self.wait() + self.play( + LaggedStart(*[ + LaggedStart(*[ + FadeInFrom(item, LEFT) + for item in sequence + ]) + for sequence in sequences + ]) + ) + self.wait() + + # Highlight 0's then 1's + for sequence in sequences[:2]: + lds = VGroup(*[item[-2] for item in sequence]) + rects = VGroup(*[ + SurroundingRectangle(ld, buff=0.05) + for ld in lds + ]) + rects.set_color(YELLOW) + self.play( + LaggedStartMap( + ShowCreationThenFadeOut, rects + ) + ) + self.wait() + + # Eliminate certain residues + two = sequences[2][0] + five = sequences[5][0] + evens = VGroup(*it.chain(*sequences[::2])) + evens.remove(two) + div5 = sequences[5][1:] + prime_numbers = read_in_primes(100) + + primes = VGroup(*[ + item + for seq in sequences + for item in seq + if int(item.get_value()) in prime_numbers + ]) + non_primes = VGroup(*[ + item + for seq in sequences + for item in seq + if reduce(op.and_, [ + int(item.get_value()) not in prime_numbers, + item.get_value() % 2 != 0, + item.get_value() % 5 != 0, + ]) + ]) + + for prime, group in [(two, evens), (five, div5)]: + self.play(ShowCreationThenFadeAround(prime)) + self.play(LaggedStart(*[ + ApplyMethod(item.set_opacity, 0.2) + for item in group + ])) + self.wait() + + # Highlight primes + self.play( + LaggedStart(*[ + ApplyFunction( + lambda m: m.scale(1.2).set_color(TEAL), + prime + ) + for prime in primes + ]), + LaggedStart(*[ + ApplyFunction( + lambda m: m.scale(0.8).set_opacity(0.8), + non_prime + ) + for non_prime in non_primes + ]), + ) + self.wait() + + # Highlight coprime residue classes + rects = VGroup(*[ + SurroundingRectangle(VGroup(labels[r], sequences[r])) + for r in [1, 3, 7, 9] + ]) + for rect in rects: + rect.reverse_points() + + fade_rect = FullScreenFadeRectangle() + fade_rect.scale(1.1) + new_fade_rect = fade_rect.copy() + fade_rect.append_vectorized_mobject(rects[0]) + for rect in rects: + new_fade_rect.append_vectorized_mobject(rect) + + self.play(DrawBorderThenFill(fade_rect)) + self.wait() + self.play( + FadeOut(fade_rect), + FadeIn(new_fade_rect), + ) + self.wait() + + +class BucketPrimesByLastDigit(Scene): + CONFIG = { + "bar_colors": [YELLOW, BLUE], + "mod": 10, + "max_n": 10000, + "n_to_animate": 20, + "n_to_show": 1000, + "x_label_scale_factor": 1, + "x_axis_label": "Last digit", + "bar_width": 0.5, + } + + def construct(self): + self.add_axes() + self.add_bars() + self.bucket_primes() + + def add_axes(self): + mod = self.mod + + axes = Axes( + x_min=0, + x_max=mod + 0.5, + x_axis_config={ + "unit_size": 10 / mod, + "include_tip": False, + }, + y_min=0, + y_max=100, + y_axis_config={ + "unit_size": 0.055, + "tick_frequency": 12.5, + "include_tip": False, + }, + ) + + x_labels = VGroup() + for x in range(mod): + digit = Integer(x) + digit.scale(self.x_label_scale_factor) + digit.next_to(axes.x_axis.n2p(x + 1), DOWN, MED_SMALL_BUFF) + x_labels.add(digit) + self.modify_x_labels(x_labels) + x_labels.set_submobject_colors_by_gradient(*self.bar_colors) + axes.add(x_labels) + axes.x_labels = x_labels + + y_labels = VGroup() + for y in range(25, 125, 25): + label = Integer(y, unit="\\%") + label.next_to(axes.y_axis.n2p(y), LEFT, MED_SMALL_BUFF) + y_labels.add(label) + axes.add(y_labels) + + x_axis_label = TextMobject(self.x_axis_label) + x_axis_label.next_to(axes.x_axis.get_end(), RIGHT, buff=MED_LARGE_BUFF) + axes.add(x_axis_label) + + y_axis_label = TextMobject("Proportion") + y_axis_label.next_to(axes.y_axis.get_end(), UP, buff=MED_LARGE_BUFF) + # y_axis_label.set_color(self.bar_colors[0]) + axes.add(y_axis_label) + + axes.center() + axes.set_width(FRAME_WIDTH - 1) + axes.to_edge(DOWN) + + self.axes = axes + self.add(axes) + + def add_bars(self): + axes = self.axes + mod = self.mod + + count_trackers = Group(*[ + ValueTracker(0) + for x in range(mod) + ]) + + bars = VGroup() + for x in range(mod): + bar = Rectangle( + height=1, + width=self.bar_width, + fill_opacity=1, + ) + bar.bottom = axes.x_axis.n2p(x + 1) + bars.add(bar) + bars.set_submobject_colors_by_gradient(*self.bar_colors) + bars.set_stroke(WHITE, 1) + + def update_bars(bars): + values = [ct.get_value() for ct in count_trackers] + total = sum(values) + if total == 0: + props = [0 for x in range(mod)] + elif total < 1: + props = values + else: + props = [value / total for value in values] + + for bar, prop in zip(bars, props): + bar.set_height( + max( + 1e-5, + 100 * prop * axes.y_axis.unit_size, + ), + stretch=True + ) + # bar.set_height(1) + bar.move_to(bar.bottom, DOWN) + + bars.add_updater(update_bars) + + self.add(count_trackers) + self.add(bars) + + self.bars = bars + self.count_trackers = count_trackers + + def bucket_primes(self): + bars = self.bars + count_trackers = self.count_trackers + + max_n = self.max_n + n_to_animate = self.n_to_animate + n_to_show = self.n_to_show + mod = self.mod + + primes = VGroup(*[ + Integer(prime).scale(2).to_edge(UP, buff=LARGE_BUFF) + for prime in read_in_primes(max_n) + ]) + + arrow = Arrow(ORIGIN, DOWN) + x_labels = self.axes.x_labels + rects = VGroup(*map(SurroundingRectangle, x_labels)) + rects.set_color(RED) + + self.play(FadeIn(primes[0])) + for i, p, np in zip(it.count(), primes[:n_to_show], primes[1:]): + d = int(p.get_value()) % mod + self.add(rects[d]) + if i < n_to_animate: + self.play( + p.scale, 0.5, + p.move_to, bars[d].get_top(), + p.set_opacity, 0, + FadeIn(np), + count_trackers[d].increment_value, 1, + ) + self.remove(p) + else: + arrow.next_to(bars[d], UP) + self.add(arrow) + self.add(p) + count_trackers[d].increment_value(1) + self.wait(0.1) + self.remove(p) + self.remove(rects[d]) + + # + def modify_x_labels(self, labels): + pass + + +class PhraseDirichletsTheoremFor10(TeacherStudentsScene): + def construct(self): + expression = TexMobject( + "\\lim_{x \\to \\infty}", + "\\left(", + "{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}", + "\\over", + "\\text{\\# of primes $p$ where $p \\le x$}}", + "\\right)", + "=", + "\\frac{1}{4}", + ) + lim, lp, num, over, denom, rp, eq, fourth = expression + expression.shift(UP) + + denom.save_state() + denom.move_to(self.hold_up_spot, DOWN) + denom.shift_onto_screen() + + num[len(denom):].set_color(YELLOW) + + x_example = VGroup( + TextMobject("Think, for example, $x = $"), + Integer(int(1e6)), + ) + x_example.arrange(RIGHT) + x_example.scale(1.5) + x_example.to_edge(UP) + + # + teacher = self.teacher + students = self.students + self.play( + FadeInFromDown(denom), + teacher.change, "raise_right_hand", + self.get_student_changes(*["pondering"] * 3), + ) + self.wait() + self.play(FadeInFromDown(x_example)) + self.wait() + self.play( + Restore(denom), + teacher.change, "thinking", + ) + self.play( + TransformFromCopy(denom, num[:len(denom)]), + Write(over), + ) + self.play( + Write(num[len(denom):]), + students[0].change, "confused", + students[2].change, "erm", + ) + self.wait(2) + self.play( + Write(lp), + Write(rp), + Write(eq), + ) + self.play(FadeInFrom(fourth, LEFT)) + self.play(FadeInFrom(lim, RIGHT)) + self.play( + ChangeDecimalToValue( + x_example[1], int(1e7), + run_time=8, + rate_func=linear, + ), + VFadeOut(x_example, run_time=8), + self.get_student_changes(*["thinking"] * 3), + Blink( + teacher, + run_time=4, + rate_func=squish_rate_func(there_and_back, 0.65, 0.7) + ), + ) + + +class InsertNewResidueClasses(Scene): + def construct(self): + nums = VGroup(*map(Integer, [3, 7, 9])) + colors = [GREEN, TEAL, BLUE] + for num, color in zip(nums, colors): + num.set_color(color) + num.add_background_rectangle(buff=SMALL_BUFF, opacity=1) + self.play(FadeInFrom(num, UP)) + self.wait() + + +class BucketPrimesBy44(BucketPrimesByLastDigit): + CONFIG = { + "mod": 44, + "n_to_animate": 5, + "x_label_scale_factor": 0.5, + "x_axis_label": "r mod 44", + "bar_width": 0.1, + } + + def modify_x_labels(self, labels): + labels[::2].set_opacity(0) + + +class BucketPrimesBy9(BucketPrimesByLastDigit): + CONFIG = { + "mod": 9, + "n_to_animate": 5, + "x_label_scale_factor": 1, + "x_axis_label": "r mod 9", + "bar_width": 1, + } + + def modify_x_labels(self, labels): + pass + + +class DirichletIn1837(MovingCameraScene): + def construct(self): + # Add timeline + dates = list(range(1780, 2030, 10)) + timeline = NumberLine( + x_min=1700, + x_max=2020, + tick_frequency=1, + numbers_with_elongated_ticks=dates, + unit_size=0.2, + stroke_color=GREY, + stroke_width=2, + ) + timeline.add_numbers( + *dates, + number_config={"group_with_commas": False}, + ) + timeline.numbers.shift(SMALL_BUFF * DOWN) + timeline.to_edge(RIGHT) + + # Special dates + d_arrow, rh_arrow, pnt_arrow = arrows = VGroup(*[ + Vector(DOWN).next_to(timeline.n2p(date), UP) + for date in [1837, 1859, 1896] + ]) + d_label, rh_label, pnt_label = labels = VGroup(*[ + TextMobject(text).next_to(arrow, UP) + for arrow, text in zip(arrows, [ + "Dirichlet's\\\\theorem\\\\1837", + "Riemann\\\\hypothesis\\\\1859", + "Prime number\\\\theorem\\\\1896", + ]) + ]) + + # Back in time + frame = self.camera_frame + self.add(timeline, arrows, labels) + self.play( + frame.move_to, timeline.n2p(1837), + run_time=4, + ) + self.wait() + + # Show picture + image = ImageMobject("Dirichlet") + image.set_height(3) + image.next_to(d_label, LEFT) + self.play(FadeInFrom(image, RIGHT)) + self.wait() + + # Flash + self.play( + Flash( + d_label.get_center(), + num_lines=12, + line_length=0.25, + flash_radius=1.5, + line_stroke_width=3, + lag_ratio=0.05, + ) + ) + self.wait() + + # Transition title + title, underline = self.get_title_and_underline() + n = len(title[0]) + self.play( + ReplacementTransform(d_label[0][:n], title[0][:n]), + FadeOut(d_label[0][n:]), + LaggedStartMap( + FadeOutAndShiftDown, Group( + image, d_arrow, + rh_label, rh_arrow, + pnt_label, pnt_arrow, + *timeline, + ) + ), + ) + self.play(ShowCreation(underline)) + self.wait() + + def get_title_and_underline(self): + frame = self.camera_frame + title = TextMobject("Dirichlet's theorem") + title.scale(1.5) + title.next_to(frame.get_top(), DOWN, buff=MED_LARGE_BUFF) + underline = Line() + underline.match_width(title) + underline.next_to(title, DOWN, SMALL_BUFF) + return title, underline + + +class PhraseDirichletsTheorem(DirichletIn1837): + def construct(self): + self.add(*self.get_title_and_underline()) + + # Copy-pasted, which isn't great + expression = TexMobject( + "\\lim_{x \\to \\infty}", + "\\left(", + "{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}", + "\\over", + "\\text{\\# of primes $p$ where $p \\le x$}}", + "\\right)", + "=", + "\\frac{1}{4}", + ) + lim, lp, num, over, denom, rp, eq, fourth = expression + expression.shift(1.5 * UP) + expression.to_edge(LEFT, MED_SMALL_BUFF) + num[len(denom):].set_color(YELLOW) + # + + # Terms and labels + ten = num[-2:] + one = num[-6] + four = fourth[-1] + + N = TexMobject("N") + r = TexMobject("r") + one_over_phi_N = TexMobject("1", "\\over", "\\phi(", "N", ")") + + N.set_color(MAROON_B) + r.set_color(BLUE) + one_over_phi_N.set_color_by_tex("N", N.get_color()) + + N.move_to(ten, DL) + r.move_to(one, DOWN) + one_over_phi_N.move_to(fourth, LEFT) + + N_label = TextMobject("$N$", " is any number") + N_label.set_color_by_tex("N", N.get_color()) + N_label.next_to(expression, DOWN, LARGE_BUFF) + + r_label = TextMobject("$r$", " is coprime to ", "$N$") + r_label[0].set_color(r.get_color()) + r_label[2].set_color(N.get_color()) + r_label.next_to(N_label, DOWN, MED_LARGE_BUFF) + + phi_N_label = TexMobject( + "\\phi({10}) = ", + "\\#\\{1, 3, 7, 9\\} = 4", + tex_to_color_map={ + "{10}": N.get_color(), + } + ) + phi_N_label[-1][2:9:2].set_color(r.get_color()) + phi_N_label.next_to(r_label, DOWN, MED_LARGE_BUFF) + # + + self.play( + LaggedStart(*[ + FadeIn(denom), + ShowCreation(over), + FadeIn(num), + Write(VGroup(lp, rp)), + FadeIn(lim), + Write(VGroup(eq, fourth)), + ]), + run_time=3, + lag_ratio=0.7, + ) + self.wait() + for mob in denom, num: + self.play(ShowCreationThenFadeAround(mob)) + self.wait() + self.play( + FadeInFrom(r, DOWN), + FadeOutAndShift(one, UP), + ) + self.play( + FadeInFrom(N, DOWN), + FadeOutAndShift(ten, UP), + ) + self.wait() + self.play( + TransformFromCopy(N, N_label[0]), + FadeIn(N_label[1:], DOWN) + ) + self.wait() + self.play( + FadeIn(r_label[1:-1], DOWN), + TransformFromCopy(r, r_label[0]), + ) + self.play( + TransformFromCopy(N_label[0], r_label[-1]), + ) + self.wait() + + self.play( + ShowCreationThenFadeAround(fourth), + ) + self.play( + FadeInFrom(one_over_phi_N[2:], LEFT), + FadeOutAndShift(four, RIGHT), + ReplacementTransform(fourth[0], one_over_phi_N[0][0]), + ReplacementTransform(fourth[1], one_over_phi_N[1][0]), + ) + self.play( + FadeInFrom(phi_N_label, DOWN) + ) + self.wait() + + # Fancier version + new_expression = TexMobject( + "\\lim_{x \\to \\infty}", + "\\left(", + "{\\pi(x; {N}, {r})", + "\\over", + "\\pi(x)}", + "\\right)", + "=", + "\\frac{1}{\\phi({N})}", + tex_to_color_map={ + "{N}": N.get_color(), + "{r}": r.get_color(), + "\\pi": WHITE, + } + ) + pis = new_expression.get_parts_by_tex("\\pi") + + randy = Randolph(height=2) + randy.next_to(new_expression, LEFT, buff=LARGE_BUFF) + randy.shift(0.75 * DOWN) + + new_expression.next_to(expression, DOWN, LARGE_BUFF) + ne_rect = SurroundingRectangle(new_expression, color=BLUE) + + label_group = VGroup(N_label, r_label) + label_group.generate_target() + label_group.target.arrange(RIGHT, buff=LARGE_BUFF) + label_group.target.next_to(new_expression, DOWN, buff=LARGE_BUFF) + + self.play( + FadeIn(randy), + ) + self.play( + randy.change, "hooray", expression + ) + self.play(Blink(randy)) + self.wait() + self.play( + FadeIn(new_expression), + MoveToTarget(label_group), + phi_N_label.to_edge, DOWN, MED_LARGE_BUFF, + randy.change, "horrified", new_expression, + ) + self.play(ShowCreation(ne_rect)) + self.play(randy.change, "confused") + self.play(Blink(randy)) + self.wait() + self.play( + LaggedStartMap( + ShowCreationThenFadeAround, pis, + ), + randy.change, "angry", new_expression + ) + self.wait() + self.play(Blink(randy)) + self.wait() + + +class MoreModestDirichlet(Scene): + def construct(self): + ed = TextMobject( + "Each (coprime) residue class ", + "is equally dense with ", + "primes." + ) + inf = TextMobject( + "Each (coprime) residue class ", + "has infinitely many ", + "primes." + ) + ed[1].set_color(BLUE) + inf[1].set_color(GREEN) + + for mob in [*ed, *inf]: + mob.save_state() + + cross = Cross(ed[1]) + c_group = VGroup(ed[1], cross) + + self.add(ed) + self.wait() + self.play(ShowCreation(cross)) + self.play( + c_group.shift, DOWN, + c_group.set_opacity, 0.5, + ReplacementTransform(ed[::2], inf[::2]), + FadeIn(inf[1]) + ) + self.wait() + self.remove(*inf) + self.play( + inf[1].shift, UP, + Restore(ed[0]), + Restore(ed[1]), + Restore(ed[2]), + FadeOut(cross), + ) + self.wait() + + +class TalkAboutProof(TeacherStudentsScene): + def construct(self): + teacher = self.teacher + students = self.students + + # Ask question + self.student_says( + "So how'd he\\\\prove it?", + student_index=0, + ) + bubble = students[0].bubble + students[0].bubble = None + self.play( + teacher.change, "hesitant", + students[1].change, "happy", + students[2].change, "happy", + ) + self.wait() + self.teacher_says( + "...er...it's a\\\\bit complicated", + target_mode="guilty", + ) + self.change_all_student_modes( + "tired", + look_at_arg=teacher.bubble, + lag_ratio=0.1, + ) + self.play( + FadeOut(bubble), + FadeOut(bubble.content), + ) + self.wait(3) + + # Bring up complex analysis + ca = TextMobject("Complex ", "Analysis") + ca.move_to(self.hold_up_spot, DOWN) + + self.play( + teacher.change, "raise_right_hand", + FadeInFromDown(ca), + FadeOut(teacher.bubble), + FadeOut(teacher.bubble.content), + self.get_student_changes(*["pondering"] * 3), + ) + self.wait() + self.play( + ca.scale, 2, + ca.center, + ca.to_edge, UP, + teacher.change, "happy", + ) + self.play(ca[1].set_color, GREEN) + self.wait(2) + self.play(ca[0].set_color, YELLOW) + self.wait(2) + self.change_all_student_modes( + "confused", look_at_arg=ca, + ) + self.wait(4) + + +class HighlightTwinPrimes(Scene): + def construct(self): + self.add_paper_titles() + self.show_twin_primes() + + def add_paper_titles(self): + gpy = TextMobject( + "Goldston, Pintz, Yildirim\\\\", + "2005", + ) + zhang = TextMobject("Zhang\\\\2014") + + gpy.move_to(FRAME_WIDTH * LEFT / 4) + gpy.to_edge(UP) + zhang.move_to(FRAME_WIDTH * RIGHT / 4) + zhang.to_edge(UP) + + self.play(LaggedStartMap( + FadeInFromDown, VGroup(gpy, zhang), + lag_ratio=0.3, + )) + + def show_twin_primes(self): + max_x = 300 + line = NumberLine( + x_min=0, + x_max=max_x, + unit_size=0.5, + numbers_with_elongated_ticks=range(10, max_x, 10), + ) + line.move_to(2.5 * DOWN + 7 * LEFT, LEFT) + line.add_numbers(*range(10, max_x, 10)) + + primes = read_in_primes(max_x) + prime_mobs = VGroup(*[ + Integer(p).next_to(line.n2p(p), UP) + for p in primes + ]) + dots = VGroup(*[Dot(line.n2p(p)) for p in primes]) + + arcs = VGroup() + for pm, npm in zip(prime_mobs, prime_mobs[1:]): + p = pm.get_value() + np = npm.get_value() + if np - p == 2: + angle = 30 * DEGREES + arc = Arc( + start_angle=angle, + angle=PI - 2 * angle, + color=RED, + ) + arc.set_width( + get_norm(npm.get_center() - pm.get_center()) + ) + arc.next_to(VGroup(pm, npm), UP, SMALL_BUFF) + arcs.add(arc) + + dots.set_color(TEAL) + prime_mobs.set_color(TEAL) + + line.add(dots) + + self.play( + FadeIn(line, lag_ratio=0.9), + LaggedStartMap(FadeInFromDown, prime_mobs), + run_time=2, + ) + line.add(prime_mobs) + self.wait() + + self.play(FadeIn(arcs)) + self.play( + line.shift, 100 * LEFT, + arcs.shift, 100 * LEFT, + run_time=20, + rate_func=lambda t: smooth(t, 5) + ) + self.wait() + + +class RandomToImportant(PiCreatureScene): + CONFIG = { + "default_pi_creature_kwargs": { + "color": GREY_BROWN, + }, + "camera_config": { + "background_color": DARKER_GREY, + } + } + + def construct(self): + morty = self.pi_creature + morty.center().to_edge(DOWN) + + left_comment = TextMobject("Arbitrary question") + left_comment.to_edge(UP) + left_comment.shift(3.5 * LEFT) + + right_comment = TextMobject("Deep fact") + right_comment.to_edge(UP) + right_comment.shift(3.5 * RIGHT) + + arrow = Arrow( + left_comment.get_right(), + right_comment.get_left(), + buff=0.5, + ) + + self.play( + morty.change, "raise_left_hand", left_comment, + FadeInFromDown(left_comment) + ) + self.wait(2) + self.play( + morty.change, "raise_right_hand", right_comment, + FadeInFromDown(right_comment) + ) + self.play( + ShowCreation(arrow), + morty.look_at, right_comment, + ) + self.wait(2) + + +class RandomWalkOfTopics(Scene): + CONFIG = { + "n_dots": 30, + "n_edge_range": [2, 4], + "super_dot_n_edges": 20, + "isolated_threashold": 0.5, + } + + def construct(self): + self.setup_network() + self.define_important() + self.perform_walk() + + def setup_network(self): + n_dots = self.n_dots + dots = VGroup() + + while len(dots) < n_dots: + point = np.random.uniform(-1, 1, size=3) + point[2] = 0 + point[0] *= 7 + point[1] *= 3 + isolated = True + for dot in dots: + if get_norm(dot.get_center() - point) < self.isolated_threashold: + isolated = False + if not isolated: + continue + dot = Dot(point) + dot.edges = VGroup() + dot.neighbors = VGroup() + dots.add(dot) + super_dot = dots[len(dots) // 2] + + all_edges = VGroup() + + def add_edge(d1, d2): + if d1 is d2: + return + edge = Line( + d1.get_center(), d2.get_center(), + buff=d1.get_width() / 2 + ) + d1.edges.add(edge) + d2.edges.add(edge) + d1.neighbors.add(d2) + d2.neighbors.add(d1) + all_edges.add(edge) + + for dot in dots: + # others = list(dots[i + 1:]) + others = [d for d in dots if d is not dot] + others.sort(key=lambda d: get_norm(d.get_center() - dot.get_center())) + n_edges = np.random.randint(*self.n_edge_range) + for other in others[:n_edges]: + if dot in other.neighbors: + continue + add_edge(dot, other) + + for dot in dots: + if len(super_dot.neighbors) > self.super_dot_n_edges: + break + elif dot in super_dot.neighbors: + continue + add_edge(super_dot, dot) + + dots.sort(lambda p: p[0]) + + all_edges.set_stroke(WHITE, 2) + dots.set_fill(LIGHT_GREY, 1) + + VGroup(dots, all_edges).to_edge(DOWN, buff=MED_SMALL_BUFF) + + self.dots = dots + self.edges = all_edges + self.super_dot = super_dot + + def define_important(self): + sd = self.super_dot + dots = self.dots + edges = self.edges + + sd.set_color(RED) + for mob in [*dots, *edges]: + mob.save_state() + mob.set_opacity(0) + + sd.set_opacity(1) + sd.edges.set_opacity(1) + sd.neighbors.set_opacity(1) + + # angles = np.arange(0, TAU, TAU / len(sd.neighbors)) + # center = 0.5 * DOWN + # sd.move_to(center) + # for dot, edge, angle in zip(sd.neighbors, sd.edges, angles): + # dot.move_to(center + rotate_vector(2.5 * RIGHT, angle)) + # if edge.get_length() > 0: + # edge.put_start_and_end_on( + # sd.get_center(), + # dot.get_center() + # ) + # rad = dot.get_width() / 2 + # llen = edge.get_length() + # edge.scale((llen - 2 * rad) / llen) + + title = VGroup( + TextMobject("Important"), + TexMobject("\\Leftrightarrow"), + TextMobject("Many connections"), + ) + title.scale(1.5) + title.arrange(RIGHT, buff=MED_LARGE_BUFF) + title.to_edge(UP) + + arrow_words = TextMobject("(in my view)") + arrow_words.set_width(2 * title[1].get_width()) + arrow_words.next_to(title[1], UP, SMALL_BUFF) + + title[0].save_state() + title[0].set_x(0) + + self.add(title[0]) + self.play( + FadeInFromLarge(sd), + title[0].set_color, RED, + ) + title[0].saved_state.set_color(RED) + self.play( + Restore(title[0]), + GrowFromCenter(title[1]), + FadeIn(arrow_words), + FadeInFrom(title[2], LEFT), + LaggedStartMap( + ShowCreation, sd.edges, + run_time=3, + ), + LaggedStartMap( + GrowFromPoint, sd.neighbors, + lambda m: (m, sd.get_center()), + run_time=3, + ), + ) + self.wait() + self.play(*map(Restore, [*dots, *edges])) + self.wait() + + def perform_walk(self): + # dots = self.dots + # edges = self.edges + sd = self.super_dot + + path = VGroup(sd) + random.seed(1) + for x in range(3): + new_choice = None + while new_choice is None or new_choice in path: + new_choice = random.choice(path[0].neighbors) + path.add_to_back(new_choice) + + for d1, d2 in zip(path, path[1:]): + self.play(Flash(d1.get_center())) + self.play( + ShowCreationThenDestruction( + Line( + d1.get_center(), + d2.get_center(), + color=YELLOW, + ) + ) + ) + + self.play(Flash(sd)) + self.play(LaggedStart(*[ + ApplyMethod( + edge.set_stroke, YELLOW, 5, + rate_func=there_and_back, + ) + for edge in sd.edges + ])) + self.wait() + + +class DeadEnds(RandomWalkOfTopics): + CONFIG = { + "n_dots": 20, + "n_edge_range": [2, 4], + "super_dot_n_edges": 2, + "random_seed": 1, + } + + def construct(self): + self.setup_network() + dots = self.dots + edges = self.edges + + self.add(dots, edges) + + VGroup( + edges[3], + edges[4], + edges[7], + edges[10], + edges[12], + edges[15], + edges[27], + edges[30], + edges[33], + ).set_opacity(0) + + # for i, edge in enumerate(edges): + # self.add(Integer(i).move_to(edge)) + + +class Rediscovery(Scene): + def construct(self): + randy = Randolph() + randy.to_edge(DOWN) + randy.shift(2 * RIGHT) + + lightbulb = Lightbulb() + lightbulb.set_stroke(width=4) + lightbulb.scale(1.5) + lightbulb.next_to(randy, UP) + + rings = self.get_rings( + lightbulb.get_center(), + max_radius=10.0, + delta_r=0.1, + ) + + bubble = ThoughtBubble() + bubble.pin_to(randy) + # bubble[-1].set_fill(GREEN_SCREEN, 0.5) + self.play( + randy.change, "pondering", + ShowCreation(bubble), + FadeInFromDown(lightbulb), + ) + self.add(rings, bubble) + self.play( + randy.change, "thinking", + LaggedStartMap( + VFadeInThenOut, + rings, + lag_ratio=0.002, + run_time=3, + ) + ) + self.wait(4) + + def get_rings(self, center, max_radius, delta_r): + radii = np.arange(0, max_radius, delta_r) + rings = VGroup(*[ + Annulus( + inner_radius=r1, + outer_radius=r2, + fill_opacity=0.75 * (1 - fdiv(r1, max_radius)), + fill_color=YELLOW + ) + for r1, r2 in zip(radii, radii[1:]) + ]) + rings.move_to(center) + return rings + + +class BePlayful(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "So be playful!", + target_mode="hooray", + ) + self.change_student_modes("thinking", "hooray", "happy") + self.wait(3) + + +class SpiralsPatronThanks(PatreonEndScreen): + CONFIG = { + "specific_patrons": [ + "Juan Benet", + "Kurt Dicus", + "Vassili Philippov", + "Burt Humburg", + "Matt Russell", + "Scott Gray", + "soekul", + "Tihan Seale", + "D. Sivakumar", + "Ali Yahya", + "Arthur Zey", + "dave nicponski", + "Joseph Kelly", + "Kaustuv DeBiswas", + "kkm", + "Lambda AI Hardware", + "Lukas Biewald", + "Mark Heising", + "Nicholas Cahill", + "Peter Mcinerney", + "Quantopian", + "Scott Walter, Ph.D.", + "Tauba Auerbach", + "Yana Chernobilsky", + "Yu Jun", + "Jordan Scales", + "Lukas -krtek.net- Novy", + "Andrew Weir", + "Britt Selvitelle", + "Britton Finley", + "David Gow", + "J", + "Jonathan Wilson", + "Joseph John Cox", + "Magnus Dahlström", + "Mattéo Delabre", + "Randy C. Will", + "Ryan Atallah", + "Luc Ritchie", + "1stViewMaths", + "Aidan Shenkman", + "Alex Mijalis", + "Alexis Olson", + "Andreas Benjamin Brössel", + "Andrew Busey", + "Andrew R. Whalley", + "Ankalagon", + "Anthony Turvey", + "Antoine Bruguier", + "Antonio Juarez", + "Arjun Chakroborty", + "Art Ianuzzi", + "Austin Goodman", + "Avi Finkel", + "Awoo", + "Azeem Ansar", + "AZsorcerer", + "Barry Fam", + "Bernd Sing", + "Boris Veselinovich", + "Bradley Pirtle", + "Brian Staroselsky", + "Calvin Lin", + "Charles Southerland", + "Charlie N", + "Chris Connett", + "Christian Kaiser", + "Clark Gaebel", + "Danger Dai", + "Daniel Herrera C", + "Daniel Pang", + "Dave B", + "Dave Kester", + "David B. Hill", + "David Clark", + "DeathByShrimp", + "Delton Ding", + "Dominik Wagner", + "eaglle", + "emptymachine", + "Eric Younge", + "Ero Carrera", + "Eryq Ouithaqueue", + "Federico Lebron", + "Fernando Via Canel", + "Frank R. Brown, Jr.", + "Giovanni Filippi", + "Hal Hildebrand", + "Hause Lin", + "Hitoshi Yamauchi", + "Ivan Sorokin", + "j eduardo perez", + "Jacob Baxter", + "Jacob Harmon", + "Jacob Hartmann", + "Jacob Magnuson", + "Jameel Syed", + "James Stevenson", + "Jason Hise", + "Jeff Linse", + "Jeff Straathof", + "John C. Vesey", + "John Griffith", + "John Haley", + "John V Wertheim", + "Jonathan Eppele", + "Josh Kinnear", + "Joshua Claeys", + "Kai-Siang Ang", + "Kanan Gill", + "Kartik Cating-Subramanian", + "L0j1k", + "Lee Redden", + "Linh Tran", + "Ludwig Schubert", + "Magister Mugit", + "Mark B Bahu", + "Mark Mann", + "Martin Price", + "Mathias Jansson", + "Matt Langford", + "Matt Roveto", + "Matthew Bouchard", + "Matthew Cocke", + "Michael Faust", + "Michael Hardel", + "Michele Donadoni", + "Mirik Gogri", + "Mustafa Mahdi", + "Márton Vaitkus", + "Nero Li", + "Nikita Lesnikov", + "Omar Zrien", + "Owen Campbell-Moore", + "Patrick Lucas", + "Pedro Igor Salomão Budib", + "Peter Ehrnstrom", + "RedAgent14", + "rehmi post", + "Rex Godby", + "Richard Barthel", + "Ripta Pasay", + "Rish Kundalia", + "Roman Sergeychik", + "Roobie", + "Ryan Williams", + "Sebastian Garcia", + "Solara570", + "Steven Siddals", + "Stevie Metke", + "Suthen Thomas", + "Tal Einav", + "Ted Suzman", + "The Responsible One", + "Thomas Tarler", + "Tianyu Ge", + "Tom Fleming", + "Tyler VanValkenburg", + "Valeriy Skobelev", + "Veritasium", + "Vinicius Reis", + "Xuanji Li", + "Yavor Ivanov", + "YinYangBalance.Asia", + ] + } + + +class Thumbnail(SpiralScene): + CONFIG = { + "max_N": 8000, + "just_show": True, + } + + def construct(self): + self.add_dots() + if not self.just_show: + pass + + def add_dots(self): + self.set_scale(scale=1e3) + + p_spiral = self.get_prime_p_spiral(self.max_N) + dots = VGroup(*[ + Dot( + point, + radius=interpolate(0.01, 0.07, min(0.5 * get_norm(point), 1)), + fill_color=TEAL, + # fill_opacity=interpolate(0.5, 1, min(get_norm(point), 1)) + ) + for point in p_spiral.points + ]) + dots.set_fill([TEAL_E, TEAL_A]) + dots.set_stroke(BLACK, 1) + + label = TextMobject( + "($p$, $p$) for all primes $p$,\\\\", + "in polar coordinates", + tex_to_color_map={ + "$p$": YELLOW, + }, + ) + + label.scale(2) + label.set_stroke(BLACK, 10, background=True) + label.add_background_rectangle_to_submobjects() + label.to_corner(DL, MED_LARGE_BUFF) + + self.add(dots) + # self.add(label) + + self.dots = dots diff --git a/old_projects/tattoo.py b/from_3b1b/old/tattoo.py similarity index 100% rename from old_projects/tattoo.py rename to from_3b1b/old/tattoo.py diff --git a/old_projects/tau_poem.py b/from_3b1b/old/tau_poem.py similarity index 100% rename from old_projects/tau_poem.py rename to from_3b1b/old/tau_poem.py diff --git a/old_projects/three_dimensions.py b/from_3b1b/old/three_dimensions.py similarity index 100% rename from old_projects/three_dimensions.py rename to from_3b1b/old/three_dimensions.py diff --git a/old_projects/triangle_of_power/end.py b/from_3b1b/old/triangle_of_power/end.py similarity index 99% rename from old_projects/triangle_of_power/end.py rename to from_3b1b/old/triangle_of_power/end.py index a477caf865..6af256e5c3 100644 --- a/old_projects/triangle_of_power/end.py +++ b/from_3b1b/old/triangle_of_power/end.py @@ -1,6 +1,6 @@ from manimlib.imports import * -from old_projects.triangle_of_power.triangle import TOP, OPERATION_COLORS +from from_3b1b.old.triangle_of_power.triangle import TOP, OPERATION_COLORS class DontLearnFromSymbols(Scene): def construct(self): diff --git a/old_projects/triangle_of_power/intro.py b/from_3b1b/old/triangle_of_power/intro.py similarity index 100% rename from old_projects/triangle_of_power/intro.py rename to from_3b1b/old/triangle_of_power/intro.py diff --git a/old_projects/triangle_of_power/triangle.py b/from_3b1b/old/triangle_of_power/triangle.py similarity index 100% rename from old_projects/triangle_of_power/triangle.py rename to from_3b1b/old/triangle_of_power/triangle.py diff --git a/old_projects/triples.py b/from_3b1b/old/triples.py similarity index 100% rename from old_projects/triples.py rename to from_3b1b/old/triples.py diff --git a/old_projects/turbulence.py b/from_3b1b/old/turbulence.py similarity index 99% rename from old_projects/turbulence.py rename to from_3b1b/old/turbulence.py index 4bbd93210f..c3765f44ed 100644 --- a/old_projects/turbulence.py +++ b/from_3b1b/old/turbulence.py @@ -1,9 +1,9 @@ from manimlib.imports import * -from old_projects.div_curl import PureAirfoilFlow -from old_projects.div_curl import move_submobjects_along_vector_field -from old_projects.div_curl import move_points_along_vector_field -from old_projects.div_curl import four_swirls_function -from old_projects.lost_lecture import ShowWord +from from_3b1b.old.div_curl import PureAirfoilFlow +from from_3b1b.old.div_curl import move_submobjects_along_vector_field +from from_3b1b.old.div_curl import move_points_along_vector_field +from from_3b1b.old.div_curl import four_swirls_function +from from_3b1b.old.lost_lecture import ShowWord class CreationDestructionMobject(VMobject): diff --git a/old_projects/uncertainty.py b/from_3b1b/old/uncertainty.py similarity index 99% rename from old_projects/uncertainty.py rename to from_3b1b/old/uncertainty.py index 99bc07cb73..f707a3f06a 100644 --- a/old_projects/uncertainty.py +++ b/from_3b1b/old/uncertainty.py @@ -2,7 +2,7 @@ import scipy from manimlib.imports import * -from old_projects.fourier import * +from from_3b1b.old.fourier import * import warnings warnings.warn(""" diff --git a/old_projects/wallis.py b/from_3b1b/old/wallis.py similarity index 100% rename from old_projects/wallis.py rename to from_3b1b/old/wallis.py diff --git a/old_projects/waves.py b/from_3b1b/old/waves.py similarity index 100% rename from old_projects/waves.py rename to from_3b1b/old/waves.py diff --git a/old_projects/wcat.py b/from_3b1b/old/wcat.py similarity index 100% rename from old_projects/wcat.py rename to from_3b1b/old/wcat.py diff --git a/old_projects/windmill.py b/from_3b1b/old/windmill.py similarity index 100% rename from old_projects/windmill.py rename to from_3b1b/old/windmill.py diff --git a/old_projects/zeta.py b/from_3b1b/old/zeta.py similarity index 100% rename from old_projects/zeta.py rename to from_3b1b/old/zeta.py diff --git a/active_projects/aliquot.py b/from_3b1b/on_hold/aliquot.py similarity index 100% rename from active_projects/aliquot.py rename to from_3b1b/on_hold/aliquot.py diff --git a/active_projects/eola2/cramer.py b/from_3b1b/on_hold/eola2/cramer.py similarity index 100% rename from active_projects/eola2/cramer.py rename to from_3b1b/on_hold/eola2/cramer.py diff --git a/active_projects/eola2/determinant_puzzle.py b/from_3b1b/on_hold/eola2/determinant_puzzle.py similarity index 100% rename from active_projects/eola2/determinant_puzzle.py rename to from_3b1b/on_hold/eola2/determinant_puzzle.py diff --git a/active_projects/eola2/gauss.py b/from_3b1b/on_hold/eola2/gauss.py similarity index 100% rename from active_projects/eola2/gauss.py rename to from_3b1b/on_hold/eola2/gauss.py diff --git a/active_projects/eop/bayes.py b/from_3b1b/on_hold/eop/bayes.py similarity index 100% rename from active_projects/eop/bayes.py rename to from_3b1b/on_hold/eop/bayes.py diff --git a/active_projects/eop/bayes_footnote.py b/from_3b1b/on_hold/eop/bayes_footnote.py similarity index 100% rename from active_projects/eop/bayes_footnote.py rename to from_3b1b/on_hold/eop/bayes_footnote.py diff --git a/active_projects/eop/birthday.py b/from_3b1b/on_hold/eop/birthday.py similarity index 100% rename from active_projects/eop/birthday.py rename to from_3b1b/on_hold/eop/birthday.py diff --git a/active_projects/eop/chapter0.py b/from_3b1b/on_hold/eop/chapter0.py similarity index 100% rename from active_projects/eop/chapter0.py rename to from_3b1b/on_hold/eop/chapter0.py diff --git a/active_projects/eop/chapter0/intro.py b/from_3b1b/on_hold/eop/chapter0/intro.py similarity index 100% rename from active_projects/eop/chapter0/intro.py rename to from_3b1b/on_hold/eop/chapter0/intro.py diff --git a/active_projects/eop/chapter1/all_sequences.py b/from_3b1b/on_hold/eop/chapter1/all_sequences.py similarity index 100% rename from active_projects/eop/chapter1/all_sequences.py rename to from_3b1b/on_hold/eop/chapter1/all_sequences.py diff --git a/active_projects/eop/chapter1/area_model_bayes.py b/from_3b1b/on_hold/eop/chapter1/area_model_bayes.py similarity index 100% rename from active_projects/eop/chapter1/area_model_bayes.py rename to from_3b1b/on_hold/eop/chapter1/area_model_bayes.py diff --git a/active_projects/eop/chapter1/area_model_erf.py b/from_3b1b/on_hold/eop/chapter1/area_model_erf.py similarity index 98% rename from active_projects/eop/chapter1/area_model_erf.py rename to from_3b1b/on_hold/eop/chapter1/area_model_erf.py index 986088436c..642f2ddb1c 100644 --- a/active_projects/eop/chapter1/area_model_erf.py +++ b/from_3b1b/on_hold/eop/chapter1/area_model_erf.py @@ -1,6 +1,6 @@ from manimlib.imports import * -from old_projects.eoc.chapter8 import * +from from_3b1b.old.eoc.chapter8 import * import scipy.special diff --git a/active_projects/eop/chapter1/area_model_expectation.py b/from_3b1b/on_hold/eop/chapter1/area_model_expectation.py similarity index 100% rename from active_projects/eop/chapter1/area_model_expectation.py rename to from_3b1b/on_hold/eop/chapter1/area_model_expectation.py diff --git a/active_projects/eop/chapter1/brick_row_scene.py b/from_3b1b/on_hold/eop/chapter1/brick_row_scene.py similarity index 100% rename from active_projects/eop/chapter1/brick_row_scene.py rename to from_3b1b/on_hold/eop/chapter1/brick_row_scene.py diff --git a/active_projects/eop/chapter1/entire_brick_wall.py b/from_3b1b/on_hold/eop/chapter1/entire_brick_wall.py similarity index 100% rename from active_projects/eop/chapter1/entire_brick_wall.py rename to from_3b1b/on_hold/eop/chapter1/entire_brick_wall.py diff --git a/active_projects/eop/chapter1/intro.py b/from_3b1b/on_hold/eop/chapter1/intro.py similarity index 100% rename from active_projects/eop/chapter1/intro.py rename to from_3b1b/on_hold/eop/chapter1/intro.py diff --git a/active_projects/eop/chapter1/just_randy_flipping_coin.py b/from_3b1b/on_hold/eop/chapter1/just_randy_flipping_coin.py similarity index 100% rename from active_projects/eop/chapter1/just_randy_flipping_coin.py rename to from_3b1b/on_hold/eop/chapter1/just_randy_flipping_coin.py diff --git a/active_projects/eop/chapter1/million_flips.py b/from_3b1b/on_hold/eop/chapter1/million_flips.py similarity index 100% rename from active_projects/eop/chapter1/million_flips.py rename to from_3b1b/on_hold/eop/chapter1/million_flips.py diff --git a/active_projects/eop/chapter1/morph_brick_row_into_histogram.py b/from_3b1b/on_hold/eop/chapter1/morph_brick_row_into_histogram.py similarity index 100% rename from active_projects/eop/chapter1/morph_brick_row_into_histogram.py rename to from_3b1b/on_hold/eop/chapter1/morph_brick_row_into_histogram.py diff --git a/active_projects/eop/chapter1/prob_dist_visuals.py b/from_3b1b/on_hold/eop/chapter1/prob_dist_visuals.py similarity index 100% rename from active_projects/eop/chapter1/prob_dist_visuals.py rename to from_3b1b/on_hold/eop/chapter1/prob_dist_visuals.py diff --git a/active_projects/eop/chapter1/quiz_result.py b/from_3b1b/on_hold/eop/chapter1/quiz_result.py similarity index 100% rename from active_projects/eop/chapter1/quiz_result.py rename to from_3b1b/on_hold/eop/chapter1/quiz_result.py diff --git a/active_projects/eop/chapter1/show_proportion.py b/from_3b1b/on_hold/eop/chapter1/show_proportion.py similarity index 100% rename from active_projects/eop/chapter1/show_proportion.py rename to from_3b1b/on_hold/eop/chapter1/show_proportion.py diff --git a/active_projects/eop/chapter1/show_uncertainty_darts.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_darts.py similarity index 100% rename from active_projects/eop/chapter1/show_uncertainty_darts.py rename to from_3b1b/on_hold/eop/chapter1/show_uncertainty_darts.py diff --git a/active_projects/eop/chapter1/show_uncertainty_dice.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_dice.py similarity index 100% rename from active_projects/eop/chapter1/show_uncertainty_dice.py rename to from_3b1b/on_hold/eop/chapter1/show_uncertainty_dice.py diff --git a/active_projects/eop/chapter1/show_uncertainty_disease.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_disease.py similarity index 100% rename from active_projects/eop/chapter1/show_uncertainty_disease.py rename to from_3b1b/on_hold/eop/chapter1/show_uncertainty_disease.py diff --git a/active_projects/eop/chapter1/stacking_coins.py b/from_3b1b/on_hold/eop/chapter1/stacking_coins.py similarity index 100% rename from active_projects/eop/chapter1/stacking_coins.py rename to from_3b1b/on_hold/eop/chapter1/stacking_coins.py diff --git a/active_projects/eop/chapter1/think_about_coin.py b/from_3b1b/on_hold/eop/chapter1/think_about_coin.py similarity index 100% rename from active_projects/eop/chapter1/think_about_coin.py rename to from_3b1b/on_hold/eop/chapter1/think_about_coin.py diff --git a/active_projects/eop/chapter1/various_intro_visuals.py b/from_3b1b/on_hold/eop/chapter1/various_intro_visuals.py similarity index 100% rename from active_projects/eop/chapter1/various_intro_visuals.py rename to from_3b1b/on_hold/eop/chapter1/various_intro_visuals.py diff --git a/active_projects/eop/chapter1/what_does_probability_mean.py b/from_3b1b/on_hold/eop/chapter1/what_does_probability_mean.py similarity index 100% rename from active_projects/eop/chapter1/what_does_probability_mean.py rename to from_3b1b/on_hold/eop/chapter1/what_does_probability_mean.py diff --git a/active_projects/eop/chapter2/permutation_grid.py b/from_3b1b/on_hold/eop/chapter2/permutation_grid.py similarity index 100% rename from active_projects/eop/chapter2/permutation_grid.py rename to from_3b1b/on_hold/eop/chapter2/permutation_grid.py diff --git a/active_projects/eop/combinations.py b/from_3b1b/on_hold/eop/combinations.py similarity index 100% rename from active_projects/eop/combinations.py rename to from_3b1b/on_hold/eop/combinations.py diff --git a/active_projects/eop/independence.py b/from_3b1b/on_hold/eop/independence.py similarity index 100% rename from active_projects/eop/independence.py rename to from_3b1b/on_hold/eop/independence.py diff --git a/active_projects/eop/pascal.py b/from_3b1b/on_hold/eop/pascal.py similarity index 100% rename from active_projects/eop/pascal.py rename to from_3b1b/on_hold/eop/pascal.py diff --git a/active_projects/eop/reusable_imports.py b/from_3b1b/on_hold/eop/reusable_imports.py similarity index 100% rename from active_projects/eop/reusable_imports.py rename to from_3b1b/on_hold/eop/reusable_imports.py diff --git a/active_projects/eop/reusables/binary_option.py b/from_3b1b/on_hold/eop/reusables/binary_option.py similarity index 100% rename from active_projects/eop/reusables/binary_option.py rename to from_3b1b/on_hold/eop/reusables/binary_option.py diff --git a/active_projects/eop/reusables/brick_row.py b/from_3b1b/on_hold/eop/reusables/brick_row.py similarity index 100% rename from active_projects/eop/reusables/brick_row.py rename to from_3b1b/on_hold/eop/reusables/brick_row.py diff --git a/active_projects/eop/reusables/coin_flip_tree.py b/from_3b1b/on_hold/eop/reusables/coin_flip_tree.py similarity index 100% rename from active_projects/eop/reusables/coin_flip_tree.py rename to from_3b1b/on_hold/eop/reusables/coin_flip_tree.py diff --git a/active_projects/eop/reusables/coin_flipping_pi_creature.py b/from_3b1b/on_hold/eop/reusables/coin_flipping_pi_creature.py similarity index 100% rename from active_projects/eop/reusables/coin_flipping_pi_creature.py rename to from_3b1b/on_hold/eop/reusables/coin_flipping_pi_creature.py diff --git a/active_projects/eop/reusables/coin_stacks.py b/from_3b1b/on_hold/eop/reusables/coin_stacks.py similarity index 100% rename from active_projects/eop/reusables/coin_stacks.py rename to from_3b1b/on_hold/eop/reusables/coin_stacks.py diff --git a/active_projects/eop/reusables/dice.py b/from_3b1b/on_hold/eop/reusables/dice.py similarity index 100% rename from active_projects/eop/reusables/dice.py rename to from_3b1b/on_hold/eop/reusables/dice.py diff --git a/active_projects/eop/reusables/eop_constants.py b/from_3b1b/on_hold/eop/reusables/eop_constants.py similarity index 100% rename from active_projects/eop/reusables/eop_constants.py rename to from_3b1b/on_hold/eop/reusables/eop_constants.py diff --git a/active_projects/eop/reusables/eop_helpers.py b/from_3b1b/on_hold/eop/reusables/eop_helpers.py similarity index 100% rename from active_projects/eop/reusables/eop_helpers.py rename to from_3b1b/on_hold/eop/reusables/eop_helpers.py diff --git a/active_projects/eop/reusables/histograms.py b/from_3b1b/on_hold/eop/reusables/histograms.py similarity index 100% rename from active_projects/eop/reusables/histograms.py rename to from_3b1b/on_hold/eop/reusables/histograms.py diff --git a/active_projects/eop/reusables/sick_pi_creature.py b/from_3b1b/on_hold/eop/reusables/sick_pi_creature.py similarity index 100% rename from active_projects/eop/reusables/sick_pi_creature.py rename to from_3b1b/on_hold/eop/reusables/sick_pi_creature.py diff --git a/active_projects/eop/reusables/upright_coins.py b/from_3b1b/on_hold/eop/reusables/upright_coins.py similarity index 100% rename from active_projects/eop/reusables/upright_coins.py rename to from_3b1b/on_hold/eop/reusables/upright_coins.py diff --git a/active_projects/eop/what_does_probability_mean.py b/from_3b1b/on_hold/eop/what_does_probability_mean.py similarity index 100% rename from active_projects/eop/what_does_probability_mean.py rename to from_3b1b/on_hold/eop/what_does_probability_mean.py diff --git a/active_projects/holomorphic.py b/from_3b1b/on_hold/holomorphic.py similarity index 100% rename from active_projects/holomorphic.py rename to from_3b1b/on_hold/holomorphic.py diff --git a/from_3b1b/on_hold/moduli.py b/from_3b1b/on_hold/moduli.py new file mode 100644 index 0000000000..7b79cdc9af --- /dev/null +++ b/from_3b1b/on_hold/moduli.py @@ -0,0 +1,928 @@ +from manimlib.imports import * + + +class TriangleModuliSpace(Scene): + CONFIG = { + "camera_config": { + "background_image": "chalkboard", + }, + "degen_color": GREEN_D, + "x1_color": GREEN_B, + "y1_color": RED, + "x_eq_y_color": YELLOW, + "right_color": TEAL, + "obtuse_color": PINK, + "acute_color": GREEN, + "triangle_fill_opacity": 0.5, + "random_seed": 0, + "example_triangle_width": 6, + } + + def setup(self): + self.plane = NumberPlane( + axis_config={ + "unit_size": 2, + } + ) + + def construct(self): + self.show_meaning_of_similar() + self.show_xy_rule() + + def show_meaning_of_similar(self): + # Setup titles + title = TextMobject("Space", " of all ", "triangles") + title.scale(1.5) + title.to_edge(UP) + + subtitle = TextMobject("up to similarity.") + subtitle.scale(1.5) + subtitle.next_to(title, DOWN, MED_SMALL_BUFF) + + question = TextMobject("What ", "is ", "a\\\\", "moduli ", "space", "?") + question.scale(2) + + # Setup all triangles + all_triangles, tri_classes = self.get_triangles_and_classes() + tri_classes[2][1].scale(0.5) + tri_classes[2][1].scalar *= 0.5 + + all_triangles.to_edge(DOWN) + + # Triangles pop up... + self.play( + LaggedStartMap(FadeInFromDown, question), + ) + self.wait() + + self.play( + ReplacementTransform( + question.get_part_by_tex("space"), + title.get_part_by_tex("Space"), + ), + FadeOut(question[:-2]), + FadeOut(question[-1]), + FadeIn(title[1:]), + LaggedStartMap( + DrawBorderThenFill, all_triangles, + rate_func=bezier([0, 0, 1.5, 1, 1]), + run_time=5, + lag_ratio=0.05, + ) + ) + self.wait() + + # ...Then divide into classes + tri_classes.generate_target() + colors = Color(BLUE).range_to(Color(RED), len(tri_classes)) + for group, color in zip(tri_classes.target, colors): + group.arrange(DOWN) + group.set_color(color) + tri_classes.target.arrange(RIGHT, buff=1.25, aligned_edge=UP) + tri_classes.target.scale(0.85) + tri_classes.target.to_corner(DL) + max_width = max([tc.get_width() for tc in tri_classes.target]) + height = tri_classes.target.get_height() + 0.5 + rects = VGroup(*[ + Rectangle( + height=height, + width=max_width + 0.25, + stroke_width=2, + ).move_to(tri_class, UP) + for tri_class in tri_classes.target + ]) + rects.shift(MED_SMALL_BUFF * UP) + + # Dumb shifts + # tri_classes.target[1][2].shift(0.25 * UP) + tri_classes.target[2].scale(0.9, about_edge=UP) + tri_classes.target[2][2].shift(0.2 * UP) + tri_classes.target[3][0].shift(0.5 * DOWN) + tri_classes.target[3][1].shift(1.0 * DOWN) + tri_classes.target[3][2].shift(1.2 * DOWN) + tri_classes.target[4][1:].shift(0.7 * UP) + + # Dots + per_class_dots = VGroup(*[ + TexMobject("\\vdots").move_to( + tri_class + ).set_y(rects.get_bottom()[1] + 0.4) + for tri_class in tri_classes.target + ]) + all_class_dots = TexMobject("\\dots").next_to( + rects, RIGHT, MED_SMALL_BUFF, + ) + + self.play( + FadeInFromDown(subtitle), + MoveToTarget(tri_classes), + ) + self.play( + LaggedStartMap(FadeIn, rects), + Write(per_class_dots), + Write(all_class_dots), + ) + self.wait(2) + + # Similar + + tri1 = tri_classes[2][1] + tri2 = tri_classes[2][2] + tri1.save_state() + tri2.save_state() + + sim_sign = TexMobject("\\sim") + sim_sign.set_width(1) + sim_sign.move_to(midpoint(rects.get_top(), TOP)) + sim_sign.shift(0.25 * DOWN) + + similar_word = TextMobject("Similar") + similar_word.scale(1.5) + similar_word.move_to(sim_sign) + similar_word.to_edge(UP) + + self.play( + FadeOutAndShift(VGroup(title, subtitle), UP), + tri1.next_to, sim_sign, LEFT, 0.75, + tri2.next_to, sim_sign, RIGHT, 0.75, + ) + self.play( + FadeInFromDown(sim_sign), + Write(similar_word, run_time=1) + ) + self.wait() + + # Move into place + tri1_copy = tri1.copy() + self.play( + tri1_copy.next_to, tri2, + RIGHT, LARGE_BUFF, + path_arc=90 * DEGREES, + ) + self.play(Rotate(tri1_copy, tri2.angle - tri1.angle)) + self.play(tri1_copy.scale, tri2.scalar / tri1.scalar) + self.play( + tri1_copy.move_to, tri2, + ) + tri1_copy.set_color(YELLOW) + self.play( + FadeOut(tri1_copy), + rate_func=rush_from, + ) + self.wait(2) + + # Show non-similar example + not_similar_word = TextMobject("Not ", "Similar") + not_similar_word.scale(1.5) + not_similar_word.move_to(similar_word) + not_similar_word.set_color(RED) + + sim_cross = Line(DL, UR) + sim_cross.set_color(RED) + sim_cross.match_width(sim_sign) + sim_cross.move_to(sim_sign) + sim_cross.set_stroke(BLACK, 5, background=True) + + tri3 = tri_classes[1][2] + tri3.save_state() + tri3.generate_target() + tri3.target.move_to(tri2, LEFT) + + tri1_copy = tri1.copy() + + self.play( + Restore(tri2), + MoveToTarget(tri3), + ) + self.play( + ReplacementTransform( + similar_word[0], + not_similar_word[1], + ), + GrowFromCenter(not_similar_word[0]), + ShowCreation(sim_cross), + ) + self.play(tri1_copy.move_to, tri3) + self.play(Rotate(tri1_copy, 90 * DEGREES)) + self.play( + tri1_copy.match_height, tri3, + tri1_copy.move_to, tri3, RIGHT, + ) + self.play(WiggleOutThenIn(tri1_copy, n_wiggles=10)) + self.play(FadeOut(tri1_copy)) + + self.wait() + + # Back to classes + new_title = TextMobject("Space of all\\\\", "Similarity classes") + new_title.scale(1.5) + new_title[1].set_color(YELLOW) + new_title.to_edge(UP) + new_title_underline = Line(LEFT, RIGHT) + new_title_underline.match_width(new_title[1]) + new_title_underline.match_color(new_title[1]) + new_title_underline.next_to(new_title, DOWN, buff=0.05) + + self.play( + Restore(tri1), + Restore(tri2), + Restore(tri3), + FadeOut(not_similar_word), + FadeOut(sim_sign), + FadeOut(sim_cross), + FadeInFrom(new_title[1], UP), + ) + self.play( + ShowCreationThenDestruction(new_title_underline), + LaggedStartMap( + ApplyMethod, rects, + lambda m: (m.set_stroke, YELLOW, 5), + rate_func=there_and_back, + run_time=1, + ) + ) + self.wait() + self.play(Write(new_title[0])) + self.wait() + + # Show abstract space + blob = ThoughtBubble()[-1] + blob.set_height(2) + blob.to_corner(UR) + + dots = VGroup(*[ + Dot(color=tri.get_color()).move_to( + self.get_triangle_x(tri) * RIGHT + + self.get_triangle_y(tri) * UP, + ) + for tri_class in tri_classes + for tri in tri_class[0] + ]) + dots.space_out_submobjects(2) + dots.move_to(blob) + + self.play( + DrawBorderThenFill(blob), + new_title.shift, LEFT, + ) + + self.play(LaggedStart( + *[ + ReplacementTransform( + tri_class.copy().set_fill(opacity=0), + dot + ) + for tri_class, dot in zip(tri_classes, dots) + ], + run_time=3, + lag_ratio=0.3, + )) + + # Isolate one triangle + + tri = tri_classes[0][0] + verts = tri.get_vertices() + angle = PI + angle_of_vector(verts[1] - verts[2]) + + self.play( + tri.rotate, -angle, + tri.set_width, self.example_triangle_width, + tri.center, + FadeOut(tri_classes[0][1:]), + FadeOut(tri_classes[1:]), + FadeOut(rects), + FadeOut(per_class_dots), + FadeOut(all_class_dots), + FadeOut(blob), + FadeOut(dots), + FadeOut(new_title), + ) + + self.triangle = tri + + def show_xy_rule(self): + unit_factor = 4.0 + + if hasattr(self, "triangle"): + triangle = self.triangle + else: + triangle = self.get_triangles_and_classes()[0][0] + verts = triangle.get_vertices() + angle = PI + angle_of_vector(verts[1] - verts[2]) + triangle.rotate(-angle) + triangle.set_width(self.example_triangle_width) + triangle.center() + self.add(triangle) + + side_trackers = VGroup(*[Line() for x in range(3)]) + side_trackers.set_stroke(width=0, opacity=0) + side_trackers.triangle = triangle + + def update_side_trackers(st): + verts = st.triangle.get_vertices() + st[0].put_start_and_end_on(verts[0], verts[1]) + st[1].put_start_and_end_on(verts[1], verts[2]) + st[2].put_start_and_end_on(verts[2], verts[0]) + + side_trackers.add_updater(update_side_trackers) + + def get_length_labels(): + result = VGroup() + for line in side_trackers: + vect = normalize(line.get_vector()) + perp_vect = rotate_vector(vect, -90 * DEGREES) + perp_vect = np.round(perp_vect, 1) + label = DecimalNumber(line.get_length() / unit_factor) + label.move_to(line.get_center()) + label.next_to(line.get_center(), perp_vect, buff=0.15) + result.add(label) + return result + + side_labels = always_redraw(get_length_labels) + + b_label, c_label, a_label = side_labels + b_side, c_side, a_side = side_trackers + + # Rescale + self.add(side_trackers) + self.play(LaggedStartMap(FadeIn, side_labels, lag_ratio=0.3, run_time=1)) + self.add(side_labels) + self.wait() + self.play(triangle.set_width, unit_factor) + self.play(ShowCreationThenFadeAround(c_label)) + self.wait() + + # Label x and y + x_label = TexMobject("x") + y_label = TexMobject("y") + xy_labels = VGroup(x_label, y_label) + xy_labels.scale(1.5) + + x_color = self.x1_color + y_color = self.y1_color + + x_label[0].set_color(x_color) + y_label[0].set_color(y_color) + + # side_labels.clear_updaters() + for var, num, vect in zip(xy_labels, [b_label, a_label], [DR, DL]): + buff = 0.15 + var.move_to(num, vect) + var.brace = Brace(num, UP) + var.brace.num = num + var.brace.add_updater( + lambda m: m.next_to(m.num, UP, buff=buff) + ) + var.add_updater( + lambda m: m.next_to(m.brace, UP, buff=buff) + ) + + var.suspend_updating() + var.brace.suspend_updating() + self.play( + FadeInFrom(var, DOWN), + Write(var.brace, run_time=1), + # MoveToTarget(num) + ) + self.wait() + + # Show plane + to_move = VGroup( + triangle, + side_labels, + x_label, + x_label.brace, + y_label, + y_label.brace, + ) + + axes = Axes( + x_min=-0.25, + x_max=1.5, + y_min=-0.25, + y_max=1.5, + axis_config={ + "tick_frequency": 0.25, + "unit_size": 3, + } + ) + x_axis = axes.x_axis + y_axis = axes.y_axis + + x_axis.add(TexMobject("x", color=x_color).next_to(x_axis, RIGHT)) + y_axis.add(TexMobject("y", color=y_color).next_to(y_axis, UP)) + + for axis, vect in [(x_axis, DOWN), (y_axis, LEFT)]: + axis.add_numbers( + 0.5, 1.0, + number_config={"num_decimal_places": 1}, + direction=vect, + ) + + axes.to_corner(DR, buff=LARGE_BUFF) + + self.play( + to_move.to_corner, UL, {"buff": LARGE_BUFF}, + to_move.shift, MED_LARGE_BUFF * DOWN, + Write(axes), + ) + + # Show coordinates + coords = VGroup(b_label.copy(), a_label.copy()) + + x_coord, y_coord = coords + x_coord.add_updater(lambda m: m.set_value(b_side.get_length() / unit_factor)) + y_coord.add_updater(lambda m: m.set_value(a_side.get_length() / unit_factor)) + + def get_coord_values(): + return [c.get_value() for c in coords] + + def get_ms_point(): + return axes.c2p(*get_coord_values()) + + dot = always_redraw( + lambda: triangle.copy().set_width(0.1).move_to(get_ms_point()) + ) + + y_line = always_redraw( + lambda: DashedLine( + x_axis.n2p(x_coord.get_value()), + get_ms_point(), + color=y_color, + stroke_width=1, + ) + ) + x_line = always_redraw( + lambda: DashedLine( + y_axis.n2p(y_coord.get_value()), + get_ms_point(), + color=x_color, + stroke_width=1, + ) + ) + + coord_label = TexMobject("(", "0.00", ",", "0.00", ")") + cl_buff = 0 + coord_label.next_to(dot, UR, buff=cl_buff) + for i, coord in zip([1, 3], coords): + coord.generate_target() + coord.target.replace(coord_label[i], dim_to_match=0) + coord_label[i].set_opacity(0) + + self.play( + MoveToTarget(x_coord), + MoveToTarget(y_coord), + FadeIn(coord_label), + ReplacementTransform(triangle.copy().set_fill(opacity=0), dot), + ) + coord_label.add(*coords) + coord_label.add_updater(lambda m: m.next_to(dot, UR, buff=cl_buff)) + self.add(x_label, y_label, dot) + self.play( + ShowCreation(x_line), + ShowCreation(y_line), + ) + self.wait() + + # Adjust triangle + tip_tracker = VectorizedPoint(triangle.points[0]) + + def update_triangle(tri): + point = tip_tracker.get_location() + tri.points[0] = point + tri.points[-1] = point + tri.make_jagged() + + triangle.add_updater(update_triangle) + + self.add(tip_tracker) + self.play(tip_tracker.shift, 0.5 * LEFT + 1.0 * UP) + self.play(tip_tracker.shift, 2.0 * DOWN) + self.play(tip_tracker.shift, 1.5 * RIGHT) + self.play(tip_tracker.shift, 1.0 * LEFT + 1.0 * UP) + self.wait() + + # Show box + t2c = {"x": x_color, "y": y_color} + ineq1 = TexMobject("0", "\\le ", "x", "\\le", "1", tex_to_color_map=t2c) + ineq2 = TexMobject("0", "\\le ", "y", "\\le", "1", tex_to_color_map=t2c) + + ineqs = VGroup(ineq1, ineq2) + ineqs.scale(1.5) + ineqs.arrange(DOWN, buff=MED_LARGE_BUFF) + ineqs.next_to(triangle, DOWN, buff=1.5) + + box = Square( + fill_color=DARK_GREY, + fill_opacity=0.75, + stroke_color=LIGHT_GREY, + stroke_width=2, + ) + box.replace(Line(axes.c2p(0, 0), axes.c2p(1, 1))) + box_outline = box.copy() + box_outline.set_fill(opacity=0) + box_outline.set_stroke(YELLOW, 3) + + self.add(box, axes, x_line, y_line, coord_label, dot) + self.play( + FadeIn(box), + LaggedStartMap(FadeInFromDown, ineqs) + ) + self.play( + ShowCreationThenFadeOut(box_outline) + ) + self.wait() + + # x >= y slice + region = Polygon( + axes.c2p(0, 0), + axes.c2p(1, 0), + axes.c2p(1, 1), + fill_color=GREY_BROWN, + fill_opacity=0.75, + stroke_color=GREY_BROWN, + stroke_width=2, + ) + region_outline = region.copy() + region_outline.set_fill(opacity=0) + region_outline.set_stroke(YELLOW, 3) + + x_eq_y_line = Line(axes.c2p(0, 0), axes.c2p(1, 1)) + x_eq_y_line.set_stroke(self.x_eq_y_color, 2) + x_eq_y_label = TexMobject("x=y", tex_to_color_map=t2c) + x_eq_y_label.next_to(x_eq_y_line.get_end(), LEFT, MED_LARGE_BUFF) + x_eq_y_label.shift(0.75 * DL) + + ineq = TexMobject("0", "\\le", "y", "\\le", "x", "\\le", "1") + ineq.set_color_by_tex("x", x_color) + ineq.set_color_by_tex("y", y_color) + ineq.scale(1.5) + ineq.move_to(ineqs, LEFT) + + self.add(region, axes, x_line, y_line, coord_label, dot) + self.play( + FadeIn(region), + ShowCreation(x_eq_y_line), + # FadeInFromDown(x_eq_y_label), + Transform(ineq1[:2], ineq[:2], remover=True), + Transform(ineq1[2:], ineq[4:], remover=True), + Transform(ineq2[:4], ineq[:4], remover=True), + Transform(ineq2[4:], ineq[6:], remover=True), + ) + self.add(ineq) + self.play(ShowCreationThenFadeOut(region_outline)) + self.wait() + + # x + y <= 1 slice + xpy1_line = Line(axes.c2p(0, 1), axes.c2p(1, 0)) + xpy1_line.set_stroke(GREEN, 2) + xpy1_label = TexMobject("x+y=1", tex_to_color_map=t2c) + xpy1_label.next_to(xpy1_line.get_start(), RIGHT, MED_LARGE_BUFF) + xpy1_label.shift(0.75 * DR) + + xpy1_ineq = TexMobject("1 \\le x + y", tex_to_color_map=t2c) + xpy1_ineq.scale(1.5) + xpy1_ineq.next_to(ineq, DOWN, buff=MED_LARGE_BUFF) + + ms_region = Polygon( + axes.c2p(1, 0), + axes.c2p(0.5, 0.5), + axes.c2p(1, 1), + fill_color=BLUE_E, + fill_opacity=0.75, + stroke_width=0, + ) + ms_outline = ms_region.copy() + ms_outline.set_fill(opacity=0) + ms_outline.set_stroke(YELLOW, 2) + + tt_line = Line(DOWN, UP, color=WHITE) + tt_line.set_height(0.25) + tt_line.add_updater(lambda m: m.move_to(tip_tracker)) + + self.play( + ShowCreation(xpy1_line), + # FadeInFrom(xpy1_label, DOWN), + FadeInFrom(xpy1_ineq, UP) + ) + self.wait() + self.play( + tip_tracker.set_y, triangle.get_bottom()[1] + 0.01, + FadeIn(tt_line), + ) + self.wait() + + self.add(ms_region, axes, x_line, y_line, coord_label, dot) + self.play( + FadeIn(ms_region), + region.set_fill, DARK_GREY, + ) + self.wait() + + # Move tip around + self.play( + tip_tracker.shift, UP + RIGHT, + FadeOut(tt_line), + ) + self.wait() + self.play(tip_tracker.shift, 0.5 * DOWN + LEFT, run_time=2) + self.wait() + self.play(tip_tracker.shift, UP + 0.7 * LEFT, run_time=2) + self.wait() + equilateral_point = triangle.get_bottom() + unit_factor * 0.5 * np.sqrt(3) * UP + self.play( + tip_tracker.move_to, + equilateral_point, + run_time=2, + ) + self.wait() + + # Label as moduli space + ms_words = TextMobject("Moduli\\\\", "Space") + ms_words.scale(1.5) + ms_words.next_to(ms_region, RIGHT, buff=0.35) + ms_arrow = Arrow( + ms_words[1].get_corner(DL), + ms_region.get_center(), + path_arc=-90 * DEGREES, + buff=0.1, + ) + # ms_arrow.rotate(-10 * DEGREES) + ms_arrow.shift(0.1 * RIGHT) + ms_arrow.scale(0.95) + + self.play( + FadeInFrom(ms_words, LEFT), + ) + self.play(ShowCreation(ms_arrow)) + self.wait() + + # Show right triangles + alpha = np.arcsin(0.8) + vect = rotate_vector(0.6 * unit_factor * LEFT, -alpha) + new_tip = triangle.get_corner(DR) + vect + + elbow = VMobject() + elbow.start_new_path(RIGHT) + elbow.add_line_to(UR) + elbow.add_line_to(UP) + + elbow.rotate(3 * TAU / 4 - alpha, about_point=ORIGIN) + elbow.scale(0.2, about_point=ORIGIN) + elbow.shift(new_tip) + + elbow_circle = Circle() + elbow_circle.replace(elbow) + elbow_circle.scale(3) + elbow_circle.move_to(new_tip) + elbow_circle.set_stroke(self.right_color, 3) + + right_words = TextMobject("Right triangle") + right_words.scale(1.5) + right_words.set_color(self.right_color) + right_words.next_to(triangle, DOWN, buff=1.5) + + ineqs = VGroup(ineq, xpy1_ineq) + + self.play( + tip_tracker.move_to, new_tip, + FadeOut(ms_words), + FadeOut(ms_arrow), + ) + self.play( + ShowCreation(elbow), + FadeInFrom(right_words, UP), + FadeOutAndShift(ineqs, DOWN), + ) + self.play( + ShowCreationThenFadeOut(elbow_circle), + ) + + # Show circular arc + pythag_eq = TexMobject("x^2 + y^2", "=", "1", tex_to_color_map=t2c) + pythag_eq.scale(1.5) + pythag_eq.next_to(right_words, DOWN, buff=MED_LARGE_BUFF) + + arc = Arc( + start_angle=90 * DEGREES, + angle=-90 * DEGREES, + color=self.right_color, + ) + arc.replace(box) + + self.play( + FadeInFrom(pythag_eq, UP), + ) + self.add(arc, arc) + self.play(ShowCreation(arc)) + self.wait() + + # Acute region + arc_piece = VMobject() + arc_piece.pointwise_become_partial(arc, 0.5, 1.0) + + acute_region = VMobject() + acute_region.start_new_path(axes.c2p(1, 1)) + acute_region.add_line_to(arc_piece.get_start()) + acute_region.append_vectorized_mobject(arc_piece) + acute_region.add_line_to(axes.c2p(1, 1)) + acute_region.set_fill(self.acute_color, 1) + acute_region.set_stroke(width=0) + + obtuse_region = VMobject() + obtuse_region.start_new_path(axes.c2p(1, 0)) + obtuse_region.add_line_to(axes.c2p(0.5, 0.5)) + obtuse_region.add_line_to(arc_piece.get_start()) + obtuse_region.append_vectorized_mobject(arc_piece) + obtuse_region.set_fill(self.obtuse_color, 1) + obtuse_region.set_stroke(width=0) + + acute_words = TextMobject("Acute triangle") + acute_words.set_color(self.acute_color) + obtuse_words = TextMobject("Obtuse triangle") + obtuse_words.set_color(self.obtuse_color) + for words in [acute_words, obtuse_words]: + words.scale(1.5) + words.move_to(right_words) + + eq = pythag_eq[-2] + gt = TexMobject(">").replace(eq) + gt.set_color(self.acute_color) + lt = TexMobject("<").replace(eq) + lt.set_color(self.obtuse_color) + + self.add(acute_region, coord_label, x_line, y_line, xpy1_line, x_eq_y_line, dot) + self.play( + tip_tracker.shift, 0.5 * UP, + coord_label.set_opacity, 0, + FadeOut(elbow), + FadeIn(acute_region), + FadeOutAndShift(right_words, UP), + FadeOutAndShift(eq, UP), + FadeInFrom(acute_words, DOWN), + FadeInFrom(gt, DOWN), + ) + self.wait() + self.play(tip_tracker.shift, 0.5 * RIGHT) + self.wait() + self.add(obtuse_region, coord_label, x_line, y_line, xpy1_line, x_eq_y_line, dot) + self.play( + tip_tracker.shift, 1.5 * DOWN, + FadeIn(obtuse_region), + FadeOutAndShift(acute_words, DOWN), + FadeOutAndShift(gt, DOWN), + FadeInFrom(obtuse_words, UP), + FadeInFrom(lt, UP), + ) + self.wait() + self.play(tip_tracker.shift, 0.5 * LEFT) + self.play(tip_tracker.shift, 0.5 * DOWN) + self.play(tip_tracker.shift, 0.5 * RIGHT) + self.play(tip_tracker.shift, 0.5 * UP) + self.wait() + + # Ambient changes + self.play( + FadeOut(obtuse_words), + FadeOut(pythag_eq[:-2]), + FadeOut(pythag_eq[-1]), + FadeOut(lt), + ) + self.play( + tip_tracker.move_to, equilateral_point + 0.25 * DL, + path_arc=30 * DEGREES, + run_time=8, + ) + + # + def get_triangles_and_classes(self): + original_triangles = VGroup(*[ + self.get_random_triangle() + for x in range(5) + ]) + original_triangles.submobjects[4] = self.get_random_triangle() # Hack + all_triangles = VGroup() + tri_classes = VGroup() + for triangle in original_triangles: + all_triangles.add(triangle) + tri_class = VGroup() + tri_class.add(triangle) + for x in range(2): + tri_copy = triangle.copy() + angle = TAU * np.random.random() + scalar = 0.25 + 1.5 * np.random.random() + + tri_copy.rotate(angle - tri_copy.angle) + tri_copy.angle = angle + tri_copy.scale(scalar / tri_copy.scalar) + tri_copy.scalar = scalar + + all_triangles.add(tri_copy) + tri_class.add(tri_copy) + tri_classes.add(tri_class) + + colors = Color(BLUE).range_to(Color(RED), len(all_triangles)) + for triangle, color in zip(all_triangles, colors): + # triangle.set_color(random_bright_color()) + triangle.set_color(color) + + all_triangles.shuffle() + all_triangles.arrange_in_grid(3, 5, buff=MED_LARGE_BUFF) + all_triangles.set_height(6) + sf = 1.25 + all_triangles.stretch(sf, 0) + for triangle in all_triangles: + triangle.stretch(1 / sf, 0) + # all_triangles.next_to(title, DOWN) + all_triangles.to_edge(DOWN, LARGE_BUFF) + + return all_triangles, tri_classes + + def get_random_triangle(self, x=None, y=None): + y = np.random.random() + x = y + np.random.random() + if x + y <= 1: + diff = 1 - (x + y) + x += diff + y += diff + tri = self.get_triangle(x, y) + tri.angle = TAU * np.random.random() + tri.scalar = 0.25 + np.random.random() * 1.5 + + tri.rotate(tri.angle) + tri.scale(tri.scalar) + return tri + + def get_triangle(self, x, y): + # Enforce assumption that x > y + if y > x: + raise Exception("Please ensure x >= y. Thank you.") + plane = self.plane + + # Heron + s = (1 + x + y) / 2.0 + area = np.sqrt(s * (s - 1.0) * (s - x) * (s - y)) + beta = np.arcsin(2 * area / x) + tip_point = RIGHT + rotate_vector(x * LEFT, -beta) + + color = self.get_triangle_color(x, y) + return Polygon( + plane.c2p(0, 0), + plane.c2p(1, 0), + plane.c2p(*tip_point[:2]), + color=color, + fill_opacity=self.triangle_fill_opacity, + ) + + def get_triangle_color(self, x, y): + epsilon = 1e-4 + if x + y == 1: + return self.x_eq_y_color + elif x == 1: + return self.x1_color + elif y == 1: + return self.y1_color + elif np.abs(x**2 + y**2 - 1) < epsilon: + return self.right_color + elif x**2 + y**2 < 1: + return self.obtuse_color + elif x**2 + y**2 > 1: + return self.acute_color + assert(False) # Should not get here + + def get_triangle_xy(self, triangle): + A, B, C = triangle.get_start_anchors()[:3] + a = get_norm(B - C) + b = get_norm(C - A) + c = get_norm(A - B) + sides = np.array(sorted([a, b, c])) + sides = sides / np.max(sides) + return sides[1], sides[0] + + def get_triangle_x(self, triangle): + return self.get_triangle_xy(triangle)[0] + + def get_triangle_y(self, triangle): + return self.get_triangle_xy(triangle)[1] + + +class Credits(Scene): + def construct(self): + items = VGroup( + TextMobject("Written by\\\\Jayadev Athreya"), + TextMobject("Illustrated and Narrated by\\\\Grant Sanderson"), + TextMobject( + "3Blue1Brown\\\\", + "\\copyright {} Copyright 2019\\\\", + "www.3blue1brown.com\\\\", + ), + ) + items.arrange(DOWN, buff=LARGE_BUFF) + + items[-1].set_color(LIGHT_GREY) + items[-1].scale(0.8, about_edge=UP) + items[-1].to_edge(DOWN) + + self.add(items[-1]) + self.play(LaggedStartMap(FadeInFromDown, items[:-1])) + self.wait() diff --git a/active_projects/shadows.py b/from_3b1b/on_hold/shadows.py similarity index 100% rename from active_projects/shadows.py rename to from_3b1b/on_hold/shadows.py diff --git a/manim.py b/manim.py index 2f659056b2..2bebaea661 100755 --- a/manim.py +++ b/manim.py @@ -3,5 +3,3 @@ if __name__ == "__main__": manimlib.main() -else: - manimlib.stream_starter.start_livestream() diff --git a/manimlib/__init__.py b/manimlib/__init__.py index 5eb9e228b8..f4274cb668 100644 --- a/manimlib/__init__.py +++ b/manimlib/__init__.py @@ -2,17 +2,10 @@ import manimlib.config import manimlib.constants import manimlib.extract_scene -import manimlib.stream_starter def main(): args = manimlib.config.parse_cli() - if not args.livestream: - config = manimlib.config.get_configuration(args) - manimlib.constants.initialize_directories(config) - manimlib.extract_scene.main(config) - else: - manimlib.stream_starter.start_livestream( - to_twitch=args.to_twitch, - twitch_key=args.twitch_key, - ) + config = manimlib.config.get_configuration(args) + manimlib.constants.initialize_directories(config) + manimlib.extract_scene.main(config) diff --git a/manimlib/animation/composition.py b/manimlib/animation/composition.py index 043765bfe9..753f5111bc 100644 --- a/manimlib/animation/composition.py +++ b/manimlib/animation/composition.py @@ -33,6 +33,7 @@ def __init__(self, *animations, **kwargs): self.group = Group(*remove_list_redundancies( [anim.mobject for anim in animations] )) + self.init_run_time() Animation.__init__(self, self.group, **kwargs) def get_all_mobjects(self): @@ -41,7 +42,7 @@ def get_all_mobjects(self): def begin(self): for anim in self.animations: anim.begin() - self.init_run_time() + # self.init_run_time() def finish(self): for anim in self.animations: diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index c88a7a4eea..692e384ecb 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -1,5 +1,7 @@ from manimlib.animation.animation import Animation +from manimlib.animation.composition import Succession from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.mobject.mobject import Group from manimlib.utils.bezier import integer_interpolate from manimlib.utils.config_ops import digest_config from manimlib.utils.rate_functions import linear @@ -7,6 +9,7 @@ from manimlib.utils.rate_functions import smooth import numpy as np +import itertools as it class ShowPartial(Animation): @@ -132,4 +135,46 @@ def __init__(self, group, **kwargs): def interpolate_mobject(self, alpha): n_submobs = len(self.all_submobs) index = int(self.int_func(alpha * n_submobs)) + self.update_submobject_list(index) + + def update_submobject_list(self, index): self.mobject.submobjects = self.all_submobs[:index] + + +class ShowSubmobjectsOneByOne(ShowIncreasingSubsets): + CONFIG = { + "int_func": np.ceil, + } + + def __init__(self, group, **kwargs): + new_group = Group(*group) + super().__init__(new_group, **kwargs) + + def update_submobject_list(self, index): + # N = len(self.all_submobs) + if index == 0: + self.mobject.submobjects = [] + else: + self.mobject.submobjects = self.all_submobs[index - 1] + + +# TODO, this is broken... +class AddTextWordByWord(Succession): + CONFIG = { + # If given a value for run_time, it will + # override the time_per_char + "run_time": None, + "time_per_char": 0.06, + } + + def __init__(self, text_mobject, **kwargs): + digest_config(self, kwargs) + tpc = self.time_per_char + anims = it.chain(*[ + [ + ShowIncreasingSubsets(word, run_time=tpc * len(word)), + Animation(word, run_time=0.005 * len(word)**1.5), + ] + for word in text_mobject + ]) + super().__init__(*anims, **kwargs) diff --git a/manimlib/animation/transform.py b/manimlib/animation/transform.py index 32871c03e0..ef07090cf5 100644 --- a/manimlib/animation/transform.py +++ b/manimlib/animation/transform.py @@ -230,7 +230,10 @@ def __init__(self, function, mobject, **kwargs): super().__init__(mobject, **kwargs) def create_target(self): - return self.function(self.mobject.copy()) + target = self.function(self.mobject.copy()) + if not isinstance(target, Mobject): + raise Exception("Functions passed to ApplyFunction must return object of type Mobject") + return target class ApplyMatrix(ApplyPointwiseFunction): diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index b6bb4be4b9..94c111eb23 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -338,15 +338,15 @@ def set_cairo_context_path(self, ctx, vmobject): return ctx.new_path() - subpaths = vmobject.get_subpaths_from_points(points) + subpaths = vmobject.gen_subpaths_from_points_2d(points) for subpath in subpaths: - quads = vmobject.get_cubic_bezier_tuples_from_points(subpath) + quads = vmobject.gen_cubic_bezier_tuples_from_points(subpath) ctx.new_sub_path() start = subpath[0] ctx.move_to(*start[:2]) for p0, p1, p2, p3 in quads: ctx.curve_to(*p1[:2], *p2[:2], *p3[:2]) - if vmobject.consider_points_equals(subpath[0], subpath[-1]): + if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): ctx.close_path() return self @@ -549,7 +549,7 @@ def adjust_out_of_range_points(self, points): def transform_points_pre_display(self, mobject, points): # Subclasses (like ThreeDCamera) may want to # adjust points futher before they're shown - if np.any(np.isnan(points)) or np.any(points == np.inf): + if not np.all(np.isfinite(points)): # TODO, print some kind of warning about # mobject having invalid points? points = np.zeros((1, 3)) diff --git a/manimlib/config.py b/manimlib/config.py index 7518d14d73..3937b6338f 100644 --- a/manimlib/config.py +++ b/manimlib/config.py @@ -11,10 +11,8 @@ def parse_cli(): try: parser = argparse.ArgumentParser() - module_location = parser.add_mutually_exclusive_group() - module_location.add_argument( + parser.add_argument( "file", - nargs="?", help="path to file holding the python code for the scene", ) parser.add_argument( @@ -129,35 +127,7 @@ def parse_cli(): "--tex_dir", help="directory to write tex", ) - - # For live streaming - module_location.add_argument( - "--livestream", - action="store_true", - help="Run in streaming mode", - ) - parser.add_argument( - "--to-twitch", - action="store_true", - help="Stream to twitch", - ) - parser.add_argument( - "--with-key", - dest="twitch_key", - help="Stream key for twitch", - ) - args = parser.parse_args() - - if args.file is None and not args.livestream: - parser.print_help() - sys.exit(2) - if args.to_twitch and not args.livestream: - print("You must run in streaming mode in order to stream to twitch") - sys.exit(2) - if args.to_twitch and args.twitch_key is None: - print("Specify the twitch stream key with --with-key") - sys.exit(2) - return args + return parser.parse_args() except argparse.ArgumentError as err: print(str(err)) sys.exit(2) diff --git a/manimlib/for_3b1b_videos/common_scenes.py b/manimlib/for_3b1b_videos/common_scenes.py index 6c417a984d..da83fbb470 100644 --- a/manimlib/for_3b1b_videos/common_scenes.py +++ b/manimlib/for_3b1b_videos/common_scenes.py @@ -153,9 +153,8 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene): "run_time": 20, "randomize_order": True, "capitalize": True, - "name_y_spacing": 0.7, - # "thanks_words": "Funded by the community, with special thanks to:", - "thanks_words": "Early access, name in credits and more at 3b1b.org/support", + "name_y_spacing": 0.6, + "thanks_words": "Find value in this? Join me in thanking these patrons:", } def construct(self): @@ -190,7 +189,6 @@ def create_pi_creatures(self): def scroll_through_patrons(self): logo_box = Square(side_length=2.5) logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) - total_width = FRAME_X_RADIUS - logo_box.get_right()[0] black_rect = Rectangle( fill_color=BLACK, @@ -214,10 +212,11 @@ def scroll_through_patrons(self): underline.next_to(thanks, DOWN, SMALL_BUFF) thanks.add(underline) - changed_patron_names = map( + changed_patron_names = list(map( self.modify_patron_name, self.specific_patrons, - ) + )) + changed_patron_names.sort() patrons = VGroup(*map( TextMobject, changed_patron_names, @@ -230,22 +229,24 @@ def scroll_through_patrons(self): VGroup(*patrons[i::self.n_patron_columns]) for i in range(self.n_patron_columns) ]) - for column in columns: + column_x_spacing = 0.5 + max([c.get_width() for c in columns]) + + for i, column in enumerate(columns): for n, name in enumerate(column): name.shift(n * self.name_y_spacing * DOWN) - columns.arrange( - RIGHT, buff=LARGE_BUFF, - aligned_edge=UP, - ) + name.align_to(ORIGIN, LEFT) + column.move_to(i * column_x_spacing * RIGHT, UL) + columns.center() + max_width = FRAME_WIDTH - 1 if columns.get_width() > max_width: columns.set_width(max_width) underline.match_width(columns) # thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) - columns.next_to(underline, DOWN, buff=2) + columns.next_to(underline, DOWN, buff=4) columns.generate_target() - columns.target.to_edge(DOWN, buff=2) + columns.target.to_edge(DOWN, buff=4) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 @@ -259,11 +260,15 @@ def scroll_through_patrons(self): self.wait(wait_time) def modify_patron_name(self, name): - if name.lower() == "RedAgent14".lower(): - return "Brian Shepetofsky" - elif name.lower() == "DeathByShrimp".lower(): - return "Henry Bresnahan" - + modification_map = { + "RedAgent14": "Brian Shepetofsky", + "DeathByShrimp": "Henry Bresnahan", + "akostrikov": "Aleksandr Kostrikov", + "Jacob Baxter": "Will Fleshman", + } + for n1, n2 in modification_map.items(): + if name.lower() == n1.lower(): + return n2 return name @@ -371,7 +376,7 @@ def get_pis(self): def get_probabalistic_message(self): return TextMobject( - "New video every", "Sunday", + "New video every ", "Sunday ", "(with probability 0.3)", tex_to_color_map={"Sunday": YELLOW}, ) diff --git a/manimlib/for_3b1b_videos/pi_creature.py b/manimlib/for_3b1b_videos/pi_creature.py index 7375c8bd2e..4e02646341 100644 --- a/manimlib/for_3b1b_videos/pi_creature.py +++ b/manimlib/for_3b1b_videos/pi_creature.py @@ -63,6 +63,7 @@ def __init__(self, mode="plain", **kwargs): except Exception: warnings.warn("No %s design with mode %s" % (self.file_name_prefix, mode)) + # TODO, this needs to change to a different, better directory svg_file = os.path.join( FILE_DIR, "PiCreatures_plain.svg", diff --git a/manimlib/for_3b1b_videos/pi_creature_animations.py b/manimlib/for_3b1b_videos/pi_creature_animations.py index 40144bc5ac..91df37739a 100644 --- a/manimlib/for_3b1b_videos/pi_creature_animations.py +++ b/manimlib/for_3b1b_videos/pi_creature_animations.py @@ -1,7 +1,7 @@ from manimlib.animation.animation import Animation from manimlib.animation.composition import AnimationGroup from manimlib.animation.fading import FadeOut -from manimlib.animation.creation import ShowCreation +from manimlib.animation.creation import DrawBorderThenFill from manimlib.animation.creation import Write from manimlib.animation.transform import ApplyMethod from manimlib.animation.transform import MoveToTarget @@ -28,7 +28,7 @@ class PiCreatureBubbleIntroduction(AnimationGroup): "target_mode": "speaking", "bubble_class": SpeechBubble, "change_mode_kwargs": {}, - "bubble_creation_class": ShowCreation, + "bubble_creation_class": DrawBorderThenFill, "bubble_creation_kwargs": {}, "bubble_kwargs": {}, "content_introduction_class": Write, diff --git a/manimlib/for_3b1b_videos/pi_creature_scene.py b/manimlib/for_3b1b_videos/pi_creature_scene.py index d5ec6c094b..def1216d7b 100644 --- a/manimlib/for_3b1b_videos/pi_creature_scene.py +++ b/manimlib/for_3b1b_videos/pi_creature_scene.py @@ -254,6 +254,9 @@ class TeacherStudentsScene(PiCreatureScene): "student_scale_factor": 0.8, "seconds_to_blink": 2, "screen_height": 3, + "camera_config": { + "background_color": DARKER_GREY, + }, } def setup(self): diff --git a/manimlib/imports.py b/manimlib/imports.py index b6fd097ba1..23c1498e85 100644 --- a/manimlib/imports.py +++ b/manimlib/imports.py @@ -86,6 +86,7 @@ from manimlib.utils.bezier import * from manimlib.utils.color import * from manimlib.utils.config_ops import * +from manimlib.utils.debug import * from manimlib.utils.images import * from manimlib.utils.iterables import * from manimlib.utils.file_ops import * diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 390e8f5ae8..442a36d745 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -128,7 +128,7 @@ def input_to_graph_point(self, x, graph): class Axes(VGroup, CoordinateSystem): CONFIG = { - "number_line_config": { + "axis_config": { "color": LIGHT_GREY, "include_tip": True, "exclude_zero_from_default_numbers": True, @@ -158,7 +158,7 @@ def __init__(self, **kwargs): def create_axis(self, min_val, max_val, axis_config): new_config = merge_dicts_recursively( - self.number_line_config, + self.axis_config, {"x_min": min_val, "x_max": max_val}, axis_config, ) @@ -187,8 +187,10 @@ def get_axes(self): return self.axes def get_coordinate_labels(self, x_vals=None, y_vals=None): - x_vals = x_vals or [] - y_vals = y_vals or [] + if x_vals is None: + x_vals = [] + if y_vals is None: + y_vals = [] x_mobs = self.get_x_axis().get_number_mobjects(*x_vals) y_mobs = self.get_y_axis().get_number_mobjects(*y_vals) @@ -282,9 +284,7 @@ class NumberPlane(Axes): } def __init__(self, **kwargs): - digest_config(self, kwargs) - kwargs["number_line_config"] = self.axis_config - Axes.__init__(self, **kwargs) + super().__init__(**kwargs) self.init_background_lines() def init_background_lines(self): diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index a66b3be941..b24e406db2 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -202,8 +202,6 @@ def get_length(self): return get_norm(start - end) - - class Arc(TipableVMobject): CONFIG = { "radius": 1.0, diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index c8eaa95949..7cd8b30f83 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -971,12 +971,6 @@ def shuffle(self, recursive=False): submob.shuffle(recursive=True) random.shuffle(self.submobjects) - def print_family(self, n_tabs=0): - """For debugging purposes""" - print("\t" * n_tabs, self, id(self)) - for submob in self.submobjects: - submob.print_family(n_tabs + 1) - # Just here to keep from breaking old scenes. def arrange_submobjects(self, *args, **kwargs): return self.arrange(*args, **kwargs) diff --git a/manimlib/mobject/shape_matchers.py b/manimlib/mobject/shape_matchers.py index a0f46229fc..a6933f218f 100644 --- a/manimlib/mobject/shape_matchers.py +++ b/manimlib/mobject/shape_matchers.py @@ -72,3 +72,14 @@ def __init__(self, mobject, **kwargs): ) self.replace(mobject, stretch=True) self.set_stroke(self.stroke_color, self.stroke_width) + + +class Underline(Line): + CONFIG = { + "buff": SMALL_BUFF, + } + + def __init__(self, mobject, **kwargs): + super().__init__(LEFT, RIGHT, **kwargs) + self.match_width(mobject) + self.next_to(mobject, DOWN, buff=self.buff) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index f437401731..866250853a 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -708,9 +708,10 @@ def add_spikes(self): endpoint=False, ) radii[:2] = radii[1::-1] # Swap first two - radii[-1] = interpolate( - radii[-1], self.pupil_radius, 0.25 - ) + if self.n_spike_layers > 2: + radii[-1] = interpolate( + radii[-1], self.pupil_radius, 0.25 + ) for radius in radii: tip_angle = self.spike_angle diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 4798003a2c..3759182f83 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -34,7 +34,7 @@ class SVGMobject(VMobject): # Must be filled in in a subclass, or when called "file_name": None, "unpack_groups": True, # if False, creates a hierarchy of VGroups - "stroke_width": 0, + "stroke_width": DEFAULT_STROKE_WIDTH, "fill_opacity": 1.0, # "fill_color" : LIGHT_GREY, } @@ -298,7 +298,7 @@ def get_all_childNodes_have_id(self, element): if not isinstance(element, minidom.Element): return if element.hasAttribute('id'): - return element + return [element] for e in element.childNodes: all_childNodes_have_id.append(self.get_all_childNodes_have_id(e)) return self.flatten([e for e in all_childNodes_have_id if e]) @@ -371,11 +371,11 @@ def handle_command(self, command, coord_string): new_points = new_points[1:] command = "L" - # Treat everything as relative line-to until empty for p in new_points: - # Treat as relative - p[0] += self.points[-1, 0] - p[1] += self.points[-1, 1] + if isLower: + # Treat everything as relative line-to until empty + p[0] += self.points[-1, 0] + p[1] += self.points[-1, 1] self.add_line_to(p) return diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index f51f158858..60b299e6d1 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -78,6 +78,10 @@ def modify_special_strings(self, tex): if tex == "": tex = "\\quad" + # To keep files from starting with a line break + if tex.startswith("\\\\"): + tex = tex.replace("\\\\", "\\quad\\\\") + # Handle imbalanced \left and \right num_lefts, num_rights = [ len([ @@ -171,8 +175,10 @@ def break_up_by_substrings(self): """ new_submobjects = [] curr_index = 0 + config = dict(self.CONFIG) + config["alignment"] = "" for tex_string in self.tex_strings: - sub_tex_mob = SingleStringTexMobject(tex_string, **self.CONFIG) + sub_tex_mob = SingleStringTexMobject(tex_string, **config) num_submobs = len(sub_tex_mob.submobjects) new_index = curr_index + num_submobs if num_submobs == 0: diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index 2be9b7589c..456e7e469e 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -9,6 +9,9 @@ from manimlib.utils.config_ops import digest_config +TEXT_MOB_SCALE_FACTOR = 0.05 + + class TextSetting(object): def __init__(self, start, end, font, slant, weight, line_num=-1): self.start = start @@ -24,6 +27,11 @@ class Text(SVGMobject): # Mobject 'color': consts.WHITE, 'height': None, + 'width': None, + 'fill_opacity': 1, + 'stroke_width': 0, + "should_center": True, + "unpack_groups": True, # Text 'font': '', 'gradient': None, @@ -45,8 +53,23 @@ def __init__(self, text, **config): self.lsh = self.size if self.lsh == -1 else self.lsh file_name = self.text2svg() + self.remove_last_M(file_name) SVGMobject.__init__(self, file_name, **config) + nppc = self.n_points_per_cubic_curve + for each in self: + if len(each.points) == 0: + continue + points = each.points + last = points[0] + each.clear_points() + for index, point in enumerate(points): + each.append_points([point]) + if index != len(points) - 1 and (index + 1) % nppc == 0 and any(point != points[index+1]): + each.add_line_to(last) + last = points[index + 1] + each.add_line_to(last) + if self.t2c: self.set_color_by_t2c() if self.gradient: @@ -55,7 +78,14 @@ def __init__(self, text, **config): self.set_color_by_t2g() # anti-aliasing - self.scale(0.1) + self.scale(TEXT_MOB_SCALE_FACTOR) + + def remove_last_M(self, file_name): + with open(file_name, 'r') as fpr: + content = fpr.read() + content = re.sub(r'Z M [^[A-Za-z]*? "\/>', 'Z "/>', content) + with open(file_name, 'w') as fpw: + fpw.write(content) def find_indexes(self, word): m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) diff --git a/manimlib/mobject/types/point_cloud_mobject.py b/manimlib/mobject/types/point_cloud_mobject.py index f7b087aba6..f8276382a7 100644 --- a/manimlib/mobject/types/point_cloud_mobject.py +++ b/manimlib/mobject/types/point_cloud_mobject.py @@ -50,6 +50,15 @@ def set_color(self, color=YELLOW_C, family=True): self.color = color return self + def get_stroke_width(self): + return self.stroke_width + + def set_stroke_width(self, width, family=True): + mobs = self.family_members_with_points() if family else [self] + for mob in mobs: + mob.stroke_width = width + return self + # def set_color_by_gradient(self, start_color, end_color): def set_color_by_gradient(self, *colors): self.rgbas = np.array(list(map( @@ -158,6 +167,12 @@ def interpolate_color(self, mobject1, mobject2, alpha): self.rgbas = interpolate( mobject1.rgbas, mobject2.rgbas, alpha ) + self.set_stroke_width(interpolate( + mobject1.get_stroke_width(), + mobject2.get_stroke_width(), + alpha, + )) + return self def pointwise_become_partial(self, mobject, a, b): lower_index, upper_index = [ @@ -206,6 +221,14 @@ def __init__(self, **kwargs): Mobject.__init__(self, **kwargs) +class PGroup(PMobject): + def __init__(self, *pmobs, **kwargs): + if not all([isinstance(m, PMobject) for m in pmobs]): + raise Exception("All submobjects must be of type PMobject") + super().__init__(**kwargs) + self.add(*pmobs) + + class PointCloudDot(Mobject1D): CONFIG = { "radius": 0.075, diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 29b91e544d..25ba527dad 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -595,35 +595,69 @@ def consider_points_equals(self, p0, p1): atol=self.tolerance_for_point_equality ) + def consider_points_equals_2d(self, p0, p1): + """ + Determine if two points are close enough to be considered equal. + + This uses the algorithm from np.isclose(), but expanded here for the + 2D point case. NumPy is overkill for such a small question. + """ + rtol = 1.e-5 # default from np.isclose() + atol = self.tolerance_for_point_equality + if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]): + return False + if abs(p0[1] - p1[1]) > atol + rtol * abs(p1[1]): + return False + return True + # Information about line def get_cubic_bezier_tuples_from_points(self, points): + return np.array(list(self.gen_cubic_bezier_tuples_from_points(points))) + + def gen_cubic_bezier_tuples_from_points(self, points): + """ + Get a generator for the cubic bezier tuples of this object. + + Generator to not materialize a list or np.array needlessly. + """ nppcc = VMobject.CONFIG["n_points_per_cubic_curve"] remainder = len(points) % nppcc points = points[:len(points) - remainder] - return np.array([ + return ( points[i:i + nppcc] for i in range(0, len(points), nppcc) - ]) + ) def get_cubic_bezier_tuples(self): return self.get_cubic_bezier_tuples_from_points( self.get_points() ) - def get_subpaths_from_points(self, points): + def _gen_subpaths_from_points(self, points, filter_func): nppcc = self.n_points_per_cubic_curve - split_indices = filter( - lambda n: not self.consider_points_equals( - points[n - 1], points[n] - ), - range(nppcc, len(points), nppcc) - ) + split_indices = filter(filter_func, range(nppcc, len(points), nppcc)) split_indices = [0] + list(split_indices) + [len(points)] - return [ + return ( points[i1:i2] for i1, i2 in zip(split_indices, split_indices[1:]) if (i2 - i1) >= nppcc - ] + ) + + def get_subpaths_from_points(self, points): + return list( + self._gen_subpaths_from_points( + points, + lambda n: not self.consider_points_equals( + points[n - 1], points[n] + )) + ) + + def gen_subpaths_from_points_2d(self, points): + return self._gen_subpaths_from_points( + points, + lambda n: not self.consider_points_equals_2d( + points[n - 1], points[n] + )) def get_subpaths(self): return self.get_subpaths_from_points(self.get_points()) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index c9ad71878e..e187b2c130 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -543,6 +543,8 @@ def add_frames(self, *frames): self.file_writer.write_frame(frame) def add_sound(self, sound_file, time_offset=0, gain=None, **kwargs): + if self.skip_animations: + return time = self.get_time() + time_offset self.file_writer.add_sound(sound_file, time, gain, **kwargs) @@ -550,17 +552,6 @@ def show_frame(self): self.update_frame(ignore_skipping=True) self.get_image().show() - # TODO, this doesn't belong in Scene, but should be - # part of some more specialized subclass optimized - # for livestreaming - def tex(self, latex): - eq = TextMobject(latex) - anims = [] - anims.append(Write(eq)) - for mobject in self.mobjects: - anims.append(ApplyMethod(mobject.shift, 2 * UP)) - self.play(*anims) - class EndSceneEarlyException(Exception): pass diff --git a/manimlib/scene/scene_file_writer.py b/manimlib/scene/scene_file_writer.py index 46875e83be..ef1586e10e 100644 --- a/manimlib/scene/scene_file_writer.py +++ b/manimlib/scene/scene_file_writer.py @@ -28,12 +28,10 @@ class SceneFileWriter(object): "save_last_frame": False, "movie_file_extension": ".mp4", "gif_file_extension": ".gif", - "livestreaming": False, - "to_twitch": False, - "twitch_key": None, # Previous output_file_name # TODO, address this in extract_scene et. al. "file_name": None, + "input_file_path": "", # ?? "output_directory": None, } @@ -170,15 +168,10 @@ def add_sound(self, sound_file, time=None, gain=None, **kwargs): def begin_animation(self, allow_write=False): if self.write_to_movie and allow_write: self.open_movie_pipe() - if self.livestreaming: - self.stream_lock = False def end_animation(self, allow_write=False): if self.write_to_movie and allow_write: self.close_movie_pipe() - if self.livestreaming: - self.stream_lock = True - thread.start_new_thread(self.idle_stream, ()) def write_frame(self, frame): if self.write_to_movie: @@ -230,38 +223,28 @@ def open_movie_pipe(self): '-pix_fmt', 'rgba', '-r', str(fps), # frames per second '-i', '-', # The imput comes from a pipe - '-c:v', 'h264_nvenc', '-an', # Tells FFMPEG not to expect any audio '-loglevel', 'error', ] + # TODO, the test for a transparent background should not be based on + # the file extension. if self.movie_file_extension == ".mov": - # This is if the background of the exported video - # should be transparent. + # This is if the background of the exported + # video should be transparent. command += [ '-vcodec', 'qtrle', - # '-vcodec', 'png', ] else: command += [ '-vcodec', 'libx264', '-pix_fmt', 'yuv420p', ] - if self.livestreaming: - if self.to_twitch: - command += ['-f', 'flv'] - command += ['rtmp://live.twitch.tv/app/' + self.twitch_key] - else: - command += ['-f', 'mpegts'] - command += [STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT] - else: - command += [temp_file_path] + command += [temp_file_path] self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE) def close_movie_pipe(self): self.writing_process.stdin.close() self.writing_process.wait() - if self.livestreaming: - return True shutil.move( self.temp_partial_movie_file_path, self.partial_movie_file_path, @@ -351,7 +334,7 @@ def combine_movie_files(self): ] subprocess.call(commands) shutil.move(temp_file_path, movie_file_path) - subprocess.call(["rm", sound_file_path]) + os.remove(sound_file_path) self.print_file_ready_message(movie_file_path) diff --git a/manimlib/scene/three_d_scene.py b/manimlib/scene/three_d_scene.py index da12061069..5c2be57f1f 100644 --- a/manimlib/scene/three_d_scene.py +++ b/manimlib/scene/three_d_scene.py @@ -109,7 +109,7 @@ class SpecialThreeDScene(ThreeDScene): }, "three_d_axes_config": { "num_axis_pieces": 1, - "number_line_config": { + "axis_config": { "unit_size": 2, "tick_frequency": 1, "numbers_with_elongated_ticks": [0, 1, 2], diff --git a/manimlib/scene/vector_space_scene.py b/manimlib/scene/vector_space_scene.py index 77a0228f3a..902ad3bd80 100644 --- a/manimlib/scene/vector_space_scene.py +++ b/manimlib/scene/vector_space_scene.py @@ -290,7 +290,7 @@ class LinearTransformationScene(VectorScene): "axis_config": { "stroke_color": LIGHT_GREY, }, - "number_line_config": { + "axis_config": { "color": GREY, }, "background_line_style": { diff --git a/manimlib/stream_starter.py b/manimlib/stream_starter.py deleted file mode 100644 index 8a77f4ea43..0000000000 --- a/manimlib/stream_starter.py +++ /dev/null @@ -1,53 +0,0 @@ -from time import sleep -import code -import os -import readline -import subprocess - -from manimlib.scene.scene import Scene -import manimlib.constants - - -def start_livestream(to_twitch=False, twitch_key=None): - class Manim(): - - def __new__(cls): - kwargs = { - "scene_name": manimlib.constants.LIVE_STREAM_NAME, - "open_video_upon_completion": False, - "show_file_in_finder": False, - # By default, write to file - "write_to_movie": True, - "show_last_frame": False, - "save_pngs": False, - # If -t is passed in (for transparent), this will be RGBA - "saved_image_mode": "RGB", - "movie_file_extension": ".mp4", - "quiet": True, - "ignore_waits": False, - "write_all": False, - "name": manimlib.constants.LIVE_STREAM_NAME, - "start_at_animation_number": 0, - "end_at_animation_number": None, - "skip_animations": False, - "camera_config": manimlib.constants.HIGH_QUALITY_CAMERA_CONFIG, - "livestreaming": True, - "to_twitch": to_twitch, - "twitch_key": twitch_key, - } - return Scene(**kwargs) - - if not to_twitch: - FNULL = open(os.devnull, 'w') - subprocess.Popen( - [manimlib.constants.STREAMING_CLIENT, manimlib.constants.STREAMING_URL], - stdout=FNULL, - stderr=FNULL) - sleep(3) - - variables = globals().copy() - variables.update(locals()) - shell = code.InteractiveConsole(variables) - shell.push("manim = Manim()") - shell.push("from manimlib.imports import *") - shell.interact(banner=manimlib.constants.STREAMING_CONSOLE_BANNER) diff --git a/manimlib/tex_template.tex b/manimlib/tex_template.tex index ede086356d..3b7cea4d46 100644 --- a/manimlib/tex_template.tex +++ b/manimlib/tex_template.tex @@ -1,6 +1,8 @@ \documentclass[preview]{standalone} \usepackage[english]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} \usepackage{amsmath} \usepackage{amssymb} \usepackage{dsfont} diff --git a/manimlib/utils/debug.py b/manimlib/utils/debug.py new file mode 100644 index 0000000000..28f8f1ad77 --- /dev/null +++ b/manimlib/utils/debug.py @@ -0,0 +1,21 @@ +from manimlib.constants import BLACK +from manimlib.mobject.numbers import Integer +from manimlib.mobject.types.vectorized_mobject import VGroup + + +def print_family(mobject, n_tabs=0): + """For debugging purposes""" + print("\t" * n_tabs, mobject, id(mobject)) + for submob in mobject.submobjects: + print_family(submob, n_tabs + 1) + + +def get_submobject_index_labels(mobject, label_height=0.15): + labels = VGroup() + for n, submob in enumerate(mobject): + label = Integer(n) + label.set_height(label_height) + label.move_to(submob) + label.set_stroke(BLACK, 5, background=True) + labels.add(label) + return labels diff --git a/old_projects/name_animation.py b/old_projects/name_animation.py deleted file mode 100644 index 8315cc6db7..0000000000 --- a/old_projects/name_animation.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - - -from manimlib.imports import * - -NAME_WITH_SPACES = "Prime Meridian" -DIAMETER = 3.0 -RADIUS = DIAMETER / 2 -LETTER_SCALE = 1 - - -class NameAnimationScene(Scene): - CONFIG = { - "animated_name": "Prime Meridian" - } - - def construct(self): - name = self.animated_name - letter_mobs = TextMobject(name) - nb_letters = len(letter_mobs) - randy = PiCreature() - randy.move_to(ORIGIN).set_height(0.5 * DIAMETER) - randy.set_color(BLUE_E) - randy.look_at(UP + RIGHT) - self.add(randy) - dtheta = TAU / nb_letters - angles = np.arange(TAU / 4, -3 * TAU / 4, -dtheta) - name_mob = VGroup() - for (letter_mob, angle) in zip(letter_mobs, angles): - letter_mob.scale(LETTER_SCALE) - pos = RADIUS * np.cos(angle) * RIGHT + RADIUS * np.sin(angle) * UP - letter_mob.move_to(pos) - name_mob.add(letter_mob) - - pos2 = RADIUS * np.cos(angles[2]) * RIGHT + \ - RADIUS * np.sin(angles[2]) * UP - - times_n_label = VGroup( - TexMobject("\\times"), - Integer(1) - ) - times_n_label.arrange(RIGHT) - times_n_label.shift(FRAME_WIDTH * RIGHT / 4) - times_n_label.to_edge(UP) - - self.play( - LaggedStartMap(FadeIn, name_mob, run_time=3), - ApplyMethod(randy.change, "pondering", pos2, run_time=1), - FadeIn(times_n_label) - ) - - for n in range(2, nb_letters + 2): - - group = [] - - for (j, letter_mob) in enumerate(name_mob.submobjects): - - new_angle = TAU / 4 - n * j * dtheta - new_pos = RADIUS * np.cos(new_angle) * \ - RIGHT + RADIUS * np.sin(new_angle) * UP - letter_mob.target = letter_mob.copy().move_to(new_pos) - anim = MoveToTarget(letter_mob, path_arc=- j * dtheta) - group.append(anim) - new_n = Integer(n) - new_n.move_to(times_n_label[1]) - self.play( - AnimationGroup(*group, run_time=3), - UpdateFromFunc(randy, lambda r: r.look_at(name_mob.submobjects[2])), - FadeOut(times_n_label[1]), - FadeIn(new_n) - ) - times_n_label.submobjects[1] = new_n - self.wait(0.5) - - thank_you = TextMobject("Thank You!").next_to(randy, DOWN) - new_randy = randy.copy() - new_randy.change("hooray") - new_randy.set_color(BLUE_E) - new_randy.look_at(ORIGIN) - self.play( - ReplacementTransform(name_mob, VGroup(*thank_you)), - Transform(randy, new_randy) - ) - self.play(Blink(randy)) - - def __str__(self): - return self.animated_name.replace(" ", "") + "Animation" - - -names = [] - -if __name__ == "__main__": - for name in names: - try: - NameAnimationScene( - frame_duration=PRODUCTION_QUALITY_FRAME_DURATION, - camera_config=PRODUCTION_QUALITY_CAMERA_CONFIG, - animated_name=name, - write_to_movie=True, - output_directory=os.path.join( - VIDEO_DIR, - "active_projects", - "name_animations", - ), - ) - except Exception as e: - print("Could not animate %s: %s" % (name, e)) diff --git a/old_projects/nn/playground.py b/old_projects/nn/playground.py deleted file mode 100644 index b10066d484..0000000000 --- a/old_projects/nn/playground.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -import os.path -from functools import reduce - -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) -from constants import * - -from manimlib.imports import * - -from nn.network import * -from nn.part1 import * - - -class Test(Scene): - def construct(self): - network = get_pretrained_network() - training_data, validation_data, test_data = load_data_wrapper() - self.show_weight_rows(network, index = 0) - # self.show_maximizing_inputs(network) - # self.show_all_activation_images(network, test_data) - - # group = Group() - # for k in range(10): - # v = np.zeros((10, 1)) - # v[k] = 1 - # h_group = Group() - # for W, b in reversed(zip(network.weights, network.biases)): - # h_group.add(MNistMobject(v)) - # v = np.dot(W.T, sigmoid_inverse(v) - b) - # v = sigmoid(v) - # h_group.add(MNistMobject(v)) - # h_group.arrange(LEFT) - # group.add(h_group) - # group.arrange(DOWN) - # group.set_height(FRAME_HEIGHT - 1) - # self.add(group) - - - def show_random_results(self): - group = Group(*[ - Group(*[ - MNistMobject(a) - for a in network.get_activation_of_all_layers( - np.random.randn(784, 1) - ) - ]).arrange(RIGHT) - for x in range(10) - ]).arrange(DOWN) - group.set_height(FRAME_HEIGHT - 1) - self.add(group) - - def show_weight_rows(self, network, index): - group = VGroup() - for row in network.weights[index]: - mob = PixelsFromVect(np.zeros(row.size)) - for n, pixel in zip(row, mob): - color = GREEN if n > 0 else RED - opacity = 2*(sigmoid(abs(n)) - 0.5) - pixel.set_fill(color, opacity = opacity) - group.add(mob) - group.arrange_in_grid() - group.set_height(FRAME_HEIGHT - 1) - self.add(group) - - def show_all_activation_images(self, network, test_data): - image_samples = Group(*[ - self.get_activation_images(digit, network, test_data) - for digit in range(10) - ]) - image_samples.arrange_in_grid( - n_rows = 2, buff = LARGE_BUFF - ) - image_samples.set_height(FRAME_HEIGHT - 1) - self.add(image_samples) - - def get_activation_images(self, digit, network, test_data, n_examples = 8): - input_vectors = [ - data[0] - for data in test_data - if data[1] == digit - ] - activation_iamges = Group(*[ - Group(*[ - MNistMobject(a) - for a in network.get_activation_of_all_layers(vect) - ]).arrange(RIGHT) - for vect in input_vectors[:n_examples] - ]).arrange(DOWN) - activation_iamges.set_height(FRAME_HEIGHT - 1) - return activation_iamges - - def show_two_blend(self): - training_data, validation_data, test_data = load_data_wrapper() - vects = [ - data[0] - for data in training_data[:30] - if np.argmax(data[1]) == 2 - ] - mean_vect = reduce(op.add, vects)/len(vects) - self.add(MNistMobject(mean_vect)) - - def show_maximizing_inputs(self, network): - training_data, validation_data, test_data = load_data_wrapper() - layer = 1 - n_neurons = DEFAULT_LAYER_SIZES[layer] - groups = Group() - for k in range(n_neurons): - out = np.zeros(n_neurons) - out[k] = 1 - in_vect = maximizing_input(network, layer, out) - new_out = network.get_activation_of_all_layers(in_vect)[layer] - group = Group(*list(map(MNistMobject, [in_vect, new_out]))) - group.arrange(DOWN+RIGHT, SMALL_BUFF) - groups.add(group) - groups.arrange_in_grid() - groups.set_height(FRAME_HEIGHT - 1) - self.add(groups) - - def show_test_input(self, network): - training_data, validation_data, test_data = load_data_wrapper() - group = Group(*[ - self.get_set(network, test) - for test in test_data[3:20] - if test[1] in [4, 9] - ]) - group.arrange(DOWN, buff = MED_LARGE_BUFF) - group.set_height(FRAME_HEIGHT - 1) - self.play(FadeIn(group)) - - def get_set(self, network, test): - test_in, test_out = test - activations = network.get_activation_of_all_layers(test_in) - group = Group(*list(map(MNistMobject, activations))) - group.arrange(RIGHT, buff = LARGE_BUFF) - return group - - # def show_frame(self): - # pass - - -if __name__ == "__main__": - save_pretrained_network() - test_network() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/perf_scenes.py b/perf_scenes.py new file mode 100644 index 0000000000..80d468e7b0 --- /dev/null +++ b/perf_scenes.py @@ -0,0 +1,88 @@ +from manimlib.imports import * + +""" +A set of scenes to be used for performance testing of Manim. +""" + + +class Perf1(GraphScene): + """ + A simple scene of two animations from the end of a video on recursion. + + - Uses a graph in 1/4 of the scene. + - First fades in multiple lines of text and equations, and the graph axes. + - Next animates creation of two graphs and the creation of their text + labels. + """ + CONFIG = { + "x_axis_label": + "$n$", + "y_axis_label": + "$time$", + "x_axis_width": + FRAME_HEIGHT, + "y_axis_height": + FRAME_HEIGHT / 2, + "y_max": + 50, + "y_min": + 0, + "x_max": + 100, + "x_min": + 0, + "x_labeled_nums": [50, 100], + "y_labeled_nums": + range(0, 51, 10), + "y_tick_frequency": + 10, + "x_tick_frequency": + 10, + "axes_color": + BLUE, + "graph_origin": + np.array( + (-FRAME_X_RADIUS + LARGE_BUFF, -FRAME_Y_RADIUS + LARGE_BUFF, 0)) + } + + def construct(self): + t1 = TextMobject( + "Dividing a problem in half over and over means\\\\" + "the work done is proportional to $\\log_2{n}$").to_edge(UP) + + t2 = TextMobject( + '\\textit{This is one of our\\\\favorite things to do in CS!}') + t2.to_edge(RIGHT) + + t3 = TextMobject( + 'The new \\texttt{power(x,n)} is \\underline{much}\\\\better than the old!' + ) + t3.scale(0.8) + p1f = TexMobject('x^n=x \\times x^{n-1}').set_color(ORANGE) + t4 = TextMobject('\\textit{vs.}').scale(0.8) + p2f = TexMobject( + 'x^n=x^{\\frac{n}{2}} \\times x^{\\frac{n}{2}}').set_color(GREEN) + p1v2g = VGroup(t3, p1f, t4, p2f).arrange(DOWN).center().to_edge(RIGHT) + + self.setup_axes() + o_n = self.get_graph(lambda x: x, color=ORANGE, x_min=1, x_max=50) + o_log2n = self.get_graph(lambda x: math.log2(x), + color=GREEN, + x_min=2, + x_max=90) + onl = TexMobject('O(n)') + olog2nl = TexMobject('O(\\log_2{n})') + onl.next_to(o_n.get_point_from_function(0.6), UL) + olog2nl.next_to(o_log2n.get_point_from_function(0.8), UP) + self.play( + FadeIn(t1), + FadeIn(self.axes), + # FadeInFromDown(t2), + FadeIn(p1v2g), + ) + self.play(ShowCreation(o_n), + ShowCreation(o_log2n), + ShowCreation(onl), + ShowCreation(olog2nl), + run_time=3) + self.wait(duration=5) diff --git a/stage_scenes.py b/stage_scenes.py index b58ea34205..8ecf07f911 100644 --- a/stage_scenes.py +++ b/stage_scenes.py @@ -45,7 +45,7 @@ def stage_scenes(module_name): animation_dir = os.path.join( os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder/videos", - "windmill", "1440p60" + "bayes", "1440p60" ) # files = os.listdir(animation_dir)