from manim_imports_ext 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 = OldTexText("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 = OldTexText("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) # Scenes to overlay on Numerphile class TableOfContents(Scene): def construct(self): rect = FullScreenFadeRectangle(opacity=0.75) self.add(rect) parts = VGroup( OldTexText("The game"), OldTexText("The puzzle"), OldTexText("The micropuzzles"), OldTexText("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 = OldTexText("``", "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 = OldTex("h") h_label.next_to(h_line, LEFT, SMALL_BUFF) chord_word = OldTexText("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 = OldTexText("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, GREY_D, TransformFromCopy(chord, chord_copy), FadeIn(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 = OldTexText("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 = OldTexText("Our distribution\\\\(uniform in the square)") left_label.match_x(group) left_label.to_edge(UP) right_label = OldTexText("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, **num_config, ) axes.y_axis.add_numbers( *numbers, **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(GREY_B, 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(OldTex("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(OldTex("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( OldTexText("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( OldTexText("\\# 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( OldTexText("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), unit="\\%" ) x_label = OldTexText("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 = OldTexText("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 = OldTex("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 = OldTex("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 = OldTex("=", "{\\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 = OldTex( 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, 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(*[ OldTex(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(*[ OldTex("\\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 = OldTex("+") 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( OldTex("< 1 \\quad \\checkmark"), OldTex("\\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 = OldTex("x") y_label = OldTex("y") z_label = OldTex("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 = OldTex( "{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 = OldTex("\\Leftrightarrow") equiv.scale(2) equiv.match_y(ineq) rhs = OldTexText( "$({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( OldTexText("First shot"), OldTex("(x_0, y_0)", **kw), ) title2 = VGroup( OldTexText("Second shot"), OldTex("(x_1, y_1)", **kw), ) title3 = VGroup( OldTexText("Point in 4d space"), OldTex("(x_0, y_0, x_1, y_1)", **kw) ) titles = VGroup(title1, title2, title3) for title in titles: title.arrange(DOWN) plus = OldTex("+").scale(2) equals = OldTex("=").scale(2) label1 = OldTex("(x_0, y_0)") label2 = OldTex("(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 = OldTex( "E[S]", "=", "1 \\cdot", "P(S = 1)", "+", "2 \\cdot", "P(S = 2)", "+", "3 \\cdot", "P(S = 3)", "+", "\\cdots", tex_to_color_map=t2c ) line2 = OldTex( "=&\\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 = OldTex( "=", "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 = OldTex("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 = OldTex("=", "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( OldTexText("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( *TexText("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 = OldTex( "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, FadeIn(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), FadeIn(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( OldTex("="), geo_frac, OldTex("= \\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, FadeIn(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 = OldTex( "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 = OldTex( "{\\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), FadeIn(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 = OldTex("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 = [ OldTexText( "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(*[ OldTex( 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), OldTex("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 = OldTex("{\\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]), FadeIn(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 = OldTexText("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(*[ OldTexText("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 = OldTexText("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 = OldTexText("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), FadeIn(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 = OldTexText("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 = OldTexText("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 = OldTexText("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), FadeOut(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 = OldTexText("\\# 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( OldTexText("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( FadeOut(score, UP), FadeIn(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, FadeOut(score, UP), FadeIn(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 = OldTexText("All points in the square are equally likely") title.scale(1.5) title.to_edge(UP) new_title = OldTexText("``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), FadeOut(self.title, UP), ) class ExpectedScoreEqualsQMark(Scene): def construct(self): equation = OldTexText( "\\textbf{E}[Score] = ???", tex_to_color_map={ "???": YELLOW, } ) aka = OldTexText("a.k.a. Long-term average") aka.next_to(equation, DOWN) self.play(Write(equation)) self.wait(2) self.play(FadeIn(aka, UP)) self.wait()