From e95acbdce795793e88e24d910bc105ba5bbd97a5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 29 Nov 2017 10:11:41 -0800 Subject: [PATCH] Beginning combinations video --- eop/combinations.py | 844 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 844 insertions(+) create mode 100644 eop/combinations.py diff --git a/eop/combinations.py b/eop/combinations.py new file mode 100644 index 00000000..11bcb88d --- /dev/null +++ b/eop/combinations.py @@ -0,0 +1,844 @@ +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.playground import * +from animation.continual_animation import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.fractals import * +from topics.number_line import * +from topics.combinatorics import * +from topics.numerals import * +from topics.three_dimensions import * +from topics.objects import * +from topics.probability import * +from topics.complex_numbers import * +from scene import Scene +from scene.reconfigurable_scene import ReconfigurableScene +from scene.zoomed_scene import * +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from topics.graph_scene import * +from topics.probability import * + +#revert_to_original_skipping_status + +def get_stack( + obj1, obj2, n, k, + fixed_start = None, + fixed_end = None, + ): + stack = VGroup() + for indices in it.combinations(range(n), k): + term = VGroup(*[ + obj1.copy() if i in indices else obj2.copy() + for i in range(n) + ]) + if fixed_start: + term.add_to_back(fixed_start.copy()) + if fixed_end: + term.add(fixed_end.copy()) + term.arrange_submobjects(RIGHT, buff = SMALL_BUFF) + stack.add(term) + stack.arrange_submobjects(DOWN) + return stack + +def get_stacks(obj1, obj2, n, **kwargs): + stacks = VGroup() + for k in range(n+1): + stacks.add(get_stack(obj1, obj2, n, k, **kwargs)) + stacks.arrange_submobjects( + RIGHT, + buff = MED_LARGE_BUFF, + aligned_edge = DOWN + ) + return stacks + +class Male(TexMobject): + CONFIG = { + "height" : 0.4, + "tex" : "\\male", + "color" : BLUE, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + TexMobject.__init__(self, self.tex, **kwargs) + self.scale_to_fit_height(self.height) + self.highlight(self.color) + +class Female(Male): + CONFIG = { + "tex" : "\\female", + "color" : MAROON_B, + } + +###################### + +class Introduction(Scene): + CONFIG = { + "start_n" : 4, + } + def construct(self): + self.write_n_choose_k() + self.show_binomial_coefficients() + self.perform_shift() + + def write_n_choose_k(self): + symbol = TexMobject("n \\choose k") + words = TextMobject("``n choose k''") + group = VGroup(symbol, words) + group.arrange_submobjects(RIGHT) + + self.play( + FadeIn(symbol), + Write(words) + ) + self.dither() + + self.set_variables_as_attrs(n_choose_k_group = group) + + def show_binomial_coefficients(self): + n = self.start_n + n_choose_k, n_choose_k_words = self.n_choose_k_group + binomials = VGroup(*[ + TexMobject("%d \\choose %d"%(n, k)) + for k in range(n+1) + ]) + binomial_equations = VGroup() + for k, binomial in enumerate(binomials): + binomial.scale(0.75) + number = TexMobject(str(choose(n, k))) + equation = VGroup(binomial, TexMobject("="), number) + equation.arrange_submobjects(RIGHT, buff = SMALL_BUFF) + equation.highlight(YELLOW) + equation[1].highlight(WHITE) + binomial_equations.add(equation) + new_words = TextMobject("``Binomial coefficients''") + + stacks = get_stacks( + TexMobject("x").highlight(BLUE), + TexMobject("y").highlight(RED), + n + ) + stacks.to_edge(DOWN, buff = LARGE_BUFF) + for stack, eq in zip(stacks, binomial_equations): + eq.scale_to_fit_width(0.9*stack.get_width()) + eq.next_to(stack, UP) + + self.play( + FadeIn(stacks, run_time = 2, submobject_mode = "lagged_start"), + self.n_choose_k_group.to_edge, UP + ) + new_words.move_to(n_choose_k_words, LEFT) + self.play(Transform(n_choose_k_words, new_words)) + for eq in binomial_equations: + point = VectorizedPoint(n_choose_k.get_center()) + self.play(ReplacementTransform( + VGroup(n_choose_k, point, point).copy(), + eq + )) + self.dither() + + self.set_variables_as_attrs(stacks, binomial_equations) + + def perform_shift(self): + n = self.start_n + to_fade = VGroup( + self.n_choose_k_group, + self.binomial_equations + ) + stacks = self.stacks + top_stacks = stacks.copy() + top_stacks.to_edge(UP, buff = MED_SMALL_BUFF) + + line = Line(LEFT, RIGHT, color = WHITE) + line.scale(SPACE_WIDTH) + line.next_to(top_stacks, DOWN) + + x = TexMobject("x").highlight(BLUE) + y = TexMobject("y").highlight(RED) + add_x, add_y = [ + TextMobject("Prepend", "$%s$"%s).highlight_by_tex(s, color) + for s, color in ("x", BLUE), ("y", RED) + ] + add_x.to_corner(UP+LEFT) + add_y.to_edge(LEFT).shift(MED_SMALL_BUFF*DOWN) + + new_stacks, new_top_stacks = [ + get_stacks(x, y, n, fixed_start = var) + for var in y, x + ] + new_top_stacks.to_edge(UP, buff = MED_SMALL_BUFF) + new_stacks.to_edge(DOWN) + for s in new_stacks, new_top_stacks: + s.start_terms = VGroup() + for stack in s: + for term in stack: + s.start_terms.add(term[0]) + + s_to_s_distance = \ + new_stacks[1].get_center()[0] - \ + new_stacks[0].get_center()[0] + + self.play( + FadeOut(to_fade), + stacks.to_edge, DOWN, + ReplacementTransform(stacks.copy(), top_stacks), + ) + self.play(ShowCreation(line)) + self.play(Write(add_x, run_time = 1)) + self.play(Transform(top_stacks, new_top_stacks)) + self.play(LaggedStart( + Indicate, new_top_stacks.start_terms, + rate_func = there_and_back, + run_time = 1, + remover = True + )) + self.dither() + self.play(Write(add_y, run_time = 1)) + self.play(Transform(stacks, new_stacks)) + self.play(LaggedStart( + Indicate, new_stacks.start_terms, + rate_func = there_and_back, + run_time = 1, + remover = True + )) + self.dither() + + self.play( + top_stacks.shift, s_to_s_distance*RIGHT/2, + stacks.shift, s_to_s_distance*LEFT/2, + ) + self.play(*map(FadeOut, [add_x, add_y, line])) + + point = VectorizedPoint() + point.move_to(top_stacks[0].get_bottom()) + point.shift(s_to_s_distance*LEFT) + top_stacks.add_to_back(point) + + point = VectorizedPoint() + point.move_to(stacks[-1].get_bottom()) + point.shift(s_to_s_distance*RIGHT) + point.shift(MED_SMALL_BUFF*DOWN) + stacks.add(point) + + for k, stack, top_stack in zip(it.count(), stacks, top_stacks): + top_stack.generate_target() + top_stack.target.next_to(stack, UP, MED_SMALL_BUFF) + # term = TexMobject( + # str(choose(n+1, k)), + # "x^%d"%(n+1-k), + # "y^%d"%k + # ) + term = TexMobject( + "{%d \\choose %d}"%(n+1, k), + "=", + str(choose(n+1, k)) + ) + term[0].scale(0.85, about_point = term[0].get_right()) + term[0].highlight(YELLOW) + term[2].highlight(YELLOW) + term.scale(0.85) + term.next_to(top_stack.target, UP) + + self.play(MoveToTarget(top_stack)) + self.play(Write(term)) + self.dither() + +class DifferentWaysToThinkAboutNChooseK(Scene): + CONFIG = { + "n" : 5, + "k" : 3, + "stack_height" : 5, + } + def construct(self): + self.add_n_choose_k_term() + self.add_stack() + self.choose_k() + self.split_stack_by_start() + self.split_choices_by_start() + + def add_n_choose_k_term(self): + term = TexMobject("{5 \\choose 3} = 10") + term.to_edge(UP) + self.play(FadeIn(term, submobject_mode = "lagged_start")) + self.dither() + + self.n_choose_k_term = term + + def add_stack(self): + n, k = self.n, self.k + x = TexMobject("x").highlight(BLUE) + y = TexMobject("y").highlight(RED) + stack = get_stack(x, y, n, k) + stack.scale_to_fit_height(self.stack_height) + stack.shift(SPACE_WIDTH*LEFT/2) + stack.to_edge(DOWN) + numbers = VGroup(*[ + TexMobject("%d"%(d+1)) + for d in range(choose(n, k)) + ]) + numbers.next_to(stack, UP) + + last_number = None + for term, number in zip(stack, numbers): + self.add(term, number) + if last_number: + self.remove(last_number) + self.dither(0.25) + last_number = number + self.dither() + + self.stack = stack + self.stack_count = last_number + self.numbers = numbers + + def choose_k(self): + n, k = self.n, self.k + + letter_set = TexMobject( + "(", + "A", ",", + "B", ",", + "C", ",", + "D", ",", + "E", ")" + ) + letters = VGroup(*letter_set[1::2]) + letter_set.shift(SPACE_WIDTH*RIGHT/2) + letter_set.to_edge(UP) + + letter_subsets = list(it.combinations(letters, k)) + subset_mobs = VGroup(*[ + VGroup(*letter_subset).copy().arrange_submobjects( + RIGHT, buff = SMALL_BUFF + ) + for letter_subset in letter_subsets + ]).arrange_submobjects(DOWN, buff = MED_SMALL_BUFF) + subset_mobs.scale_to_fit_height(self.stack_height) + subset_mobs.shift(SPACE_WIDTH*RIGHT/2) + subset_mobs.to_edge(DOWN) + + choose_words = TextMobject("Choose %d"%k) + choose_words.scale(0.9) + choose_words.next_to(letter_set, DOWN) + choose_words.highlight(YELLOW) + + self.revert_to_original_skipping_status() + self.play(Write(letter_set, run_time = 1)) + self.play( + Write(choose_words, run_time = 1), + LaggedStart(FadeIn, subset_mobs) + ) + self.dither() + for subset, subset_mob in zip(letter_subsets, subset_mobs): + VGroup(subset_mob, *subset).highlight(BLUE) + self.dither(0.5) + VGroup(*subset).highlight(WHITE) + self.dither() + + self.set_variables_as_attrs( + subset_mobs, letter_set, choose_words, + ) + + def split_stack_by_start(self): + n, k = self.n, self.k + stack = self.stack + stack_count = self.stack_count + + top_num = choose(n-1, k-1) + top_stack = VGroup(*stack[:top_num]) + bottom_stack = VGroup(*stack[top_num:]) + + self.play( + FadeOut(stack_count), + top_stack.shift, UP + ) + for stack, new_k in (top_stack, k-1), (bottom_stack, k): + brace = Brace(stack, RIGHT) + brace_tex = brace.get_tex( + "{%d \\choose %d} = %d"%(n-1, new_k, choose(n-1, new_k)) + ) + rect = SurroundingRectangle(VGroup(*[ + VGroup(*term[1:]) + for term in stack + ]), buff = 0.5*SMALL_BUFF) + rect.set_stroke(WHITE, 2) + self.play( + GrowFromCenter(brace), + Write(brace_tex), + ShowCreation(rect) + ) + self.dither() + + def split_choices_by_start(self): + subset_mobs = self.subset_mobs + subset_mobs.generate_target() + subset_mobs.target.shift(LEFT) + brace = Brace(subset_mobs.target, RIGHT) + expression = brace.get_tex( + "\\frac{5 \\cdot 4 \\cdot 3}{1 \\cdot 2 \\cdot 3}", + "= 10" + ) + + self.play( + MoveToTarget(subset_mobs), + GrowFromCenter(brace) + ) + self.play(Write(expression)) + self.dither() + +class FormulaVsPattern(TeacherStudentsScene): + def construct(self): + self.show_formula() + self.show_pattern() + + def show_formula(self): + formula = TexMobject( + "{n \\choose k} = {n! \\over (n-k)!k!}", + ) + for i in 1, 5, 9: + formula[i].highlight(BLUE) + for i in 2, 11, 14: + formula[i].highlight(YELLOW) + + self.student_thinks(formula, student_index = 1) + self.play(self.teacher.change, "sassy") + self.dither(2) + self.play( + FadeOut(self.students[1].bubble), + FadeOut(formula), + self.teacher.change, "raise_right_hand", + self.get_student_changes(*["pondering"]*3) + ) + + def show_pattern(self): + words = TextMobject( + "What is the \\\\ probability of a flush?" + ) + values = random.sample(PlayingCard.CONFIG["possible_values"], 5) + cards = VGroup(*[ + PlayingCard(value = value, suit = "hearts") + for value in values + ]) + cards.arrange_submobjects(RIGHT) + cards.to_corner(UP+RIGHT) + words.next_to(cards, LEFT) + words.shift_onto_screen() + + self.play(LaggedStart(DrawBorderThenFill, cards)) + self.play(Write(words)) + self.dither(3) + +class ProbabilityOfThreeWomenInGroupOfFive(Scene): + CONFIG = { + "random_seed" : 9, + "n_people_per_lineup" : 5, + "n_start_examples" : 7, + "n_secondary_examples" : 8, + "time_per_secondary_example" : 0.75, + "item_line_width" : 0.4, + } + def construct(self): + random.seed(self.random_seed) + self.introduce_random_choices() + self.mention_all_possibilities() + self.show_all_possibilities() + self.show_all_configurations_with_three_women() + self.stack_all_choices_by_number_of_women() + self.go_through_stacks() + self.remember_this_sensation() + self.show_answer_to_question() + self.ask_about_pattern() + + def introduce_random_choices(self): + title = TextMobject("5 randomly chosen people") + title.to_edge(UP, buff = MED_SMALL_BUFF) + self.add(title) + + question = TextMobject( + "Probability of having \\\\ exactly 3 women?" + ) + question.highlight(YELLOW) + + lineups = VGroup() + for x in range(self.n_start_examples): + lineup = self.get_random_lineup_of_men_and_women() + lineup.scale(1.5) + lineup.center() + women = VGroup(*filter( + lambda item : "female" in item.get_tex_string(), + lineup.items + )) + anims = [FadeIn(lineup, submobject_mode = "lagged_start")] + to_fade = VGroup(lineup) + if x == 2: + question.next_to(lineup, DOWN, buff = MED_LARGE_BUFF) + anims.append(Write(question)) + if x >= 2: + arrows = VGroup(*[ + Vector(DOWN).next_to(w, UP) + for w in women + ]) + arrows.highlight(MAROON_B) + words = TextMobject("%d women"%len(women)) + words.highlight(MAROON_B) + words.next_to(arrows, UP) + anims += [ + LaggedStart(GrowArrow, arrows), + Write(words) + ] + to_fade.add(arrows, words) + self.play(*anims, run_time = 1) + self.dither() + self.play(FadeOut(to_fade)) + self.play(question.next_to, title, DOWN) + + self.title = title + self.question = question + + def mention_all_possibilities(self): + words = TextMobject("What are all \\\\ the possibilities?") + + last_lineup = None + for x in xrange(self.n_secondary_examples): + lineup = self.get_random_lineup_of_men_and_women() + lineup.scale(1.5) + lineup.move_to(ORIGIN, DOWN) + run_time = self.time_per_secondary_example + if last_lineup is None: + words.next_to(lineup, DOWN, MED_LARGE_BUFF) + self.play( + FadeIn(lineup), + FadeIn(words), + run_time = run_time + ) + else: + self.play( + last_lineup.items.shift, UP, + last_lineup.items.fade, 1, + *map(GrowFromCenter, lineup.items), + run_time = run_time + ) + self.remove(last_lineup) + self.add(lineup) + last_lineup = lineup + + self.lineup = last_lineup + self.all_possibilities_words = words + + def show_all_possibilities(self): + man, woman = Male(), Female() + + vects = [ + 1.5*UP, + 0.7*UP, + 0.3*UP, + 3.5*RIGHT, + 1.5*RIGHT, + ] + lineup_groups = VGroup() + for k in range(6): + lineup_group = VGroup() + for tup in it.product(*[[man, woman]]*k): + lineup = self.get_lineup(*list(tup) + (5-k)*[None]) + lineup.scale(1.4*(0.9)**k) + lineup.move_to(0.5*DOWN) + for mob, vect in zip(tup, vects): + if mob is woman: + lineup.shift(vect) + else: + lineup.shift(-vect) + lineup_group.add(lineup) + lineup_groups.add(lineup_group) + + n_possibilities = TexMobject( + "2 \\cdot", "2 \\cdot", "2 \\cdot", "2 \\cdot", "2", + "\\text{ Possibilities}" + ) + n_possibilities.next_to(self.title, DOWN) + twos = VGroup(*n_possibilities[-2::-1]) + two_anims = [ + ReplacementTransform( + VectorizedPoint(twos[0].get_center()), + twos[0] + ) + ] + [ + ReplacementTransform(t1.copy(), t2) + for t1, t2 in zip(twos, twos[1:]) + ] + + curr_lineup_group = lineup_groups[0] + self.play( + ReplacementTransform(self.lineup, curr_lineup_group[0]), + FadeOut(self.all_possibilities_words), + FadeOut(self.question) + ) + for i, lineup_group in enumerate(lineup_groups[1:]): + anims = [ReplacementTransform(curr_lineup_group, lineup_group)] + anims += two_anims[:i+1] + if i == 0: + anims.append(FadeIn(n_possibilities[-1])) + self.remove(twos) + self.play(*anims) + + men, women = VGroup(), VGroup() + for lineup in lineup_group: + item = lineup.items[i] + if "female" in item.get_tex_string(): + women.add(item) + else: + men.add(item) + for group in men, women: + self.play(LaggedStart( + ApplyMethod, group, + lambda m : (m.shift, MED_SMALL_BUFF*RIGHT), + rate_func = there_and_back, + lag_ratio = 0.9**i, + run_time = 1, + )) + self.dither() + curr_lineup_group = lineup_group + self.lineups = curr_lineup_group + + eq_32 = TexMobject("=", "32") + eq_32.move_to(twos.get_right()) + eq_32.highlight_by_tex("32", YELLOW) + self.play( + n_possibilities[-1].next_to, eq_32, RIGHT, + twos.next_to, eq_32, LEFT, + FadeIn(eq_32), + ) + self.dither() + + n_possibilities.add(*eq_32) + self.set_variables_as_attrs(n_possibilities) + + def show_all_configurations_with_three_women(self): + lineups = self.lineups + items_to_fade = VGroup() + lines_to_fade = VGroup() + women_triplets = VGroup() + for lineup in lineups: + lineup.women = VGroup(*filter( + lambda item : "female" in item.get_tex_string(), + lineup.items + )) + if len(lineup.women) == 3: + women_triplets.add(*lineup.women) + else: + items_to_fade.add(lineup.items) + lines_to_fade.add(lineup.lines) + + self.play( + lines_to_fade.set_stroke, LIGHT_GREY, 1, + items_to_fade.set_fill, None, 0.3, + ) + self.play( + women_triplets.highlight, YELLOW, + run_time = 2, + rate_func = there_and_back + ) + self.dither() + + def stack_all_choices_by_number_of_women(self): + lineups = self.lineups + stacks = VGroup(*[VGroup() for x in range(6)]) + for lineup in lineups: + stacks[len(lineup.women)].add(lineup) + stacks.generate_target() + stacks.target.scale(0.75) + for stack in stacks.target: + stack.arrange_submobjects(DOWN, buff = 2*SMALL_BUFF) + stacks.target.arrange_submobjects( + RIGHT, buff = MED_LARGE_BUFF, aligned_edge = DOWN + ) + stacks.target.to_edge(DOWN) + + self.play(MoveToTarget( + stacks, + run_time = 2, + path_arc = np.pi/2 + )) + self.dither() + + self.stacks = stacks + + def go_through_stacks(self): + stacks = self.stacks + numbers = VGroup() + for stack in stacks: + items = VGroup() + lines = VGroup() + women = VGroup() + for lineup in stack: + items.add(lineup.items) + lines.add(lineup.lines) + for item in lineup.items: + if "female" in item.get_tex_string(): + women.add(item) + number = TexMobject(str(len(stack))) + number.highlight(YELLOW) + number.next_to(stack, UP) + numbers.add(number) + + self.play( + items.set_fill, None, 1, + lines.set_stroke, WHITE, 3, + Write(number) + ) + self.play(LaggedStart(Indicate, women, rate_func = there_and_back)) + self.dither() + + self.numbers = numbers + + def remember_this_sensation(self): + n_possibilities = self.n_possibilities + n_possibilities_rect = SurroundingRectangle(n_possibilities) + twos = VGroup(*n_possibilities[:5]) + numbers = self.numbers + + self.play(ShowCreation(n_possibilities_rect)) + self.play(LaggedStart( + Indicate, twos, + rate_func = wiggle + )) + self.play(FadeOut(n_possibilities_rect)) + for number in numbers: + self.play(Indicate(number, color = PINK, run_time = 0.5)) + self.dither() + + def show_answer_to_question(self): + stacks = self.stacks + numbers = self.numbers + n_possibilities = self.n_possibilities + three_stack = stacks[3] + to_fade = VGroup(*filter( + lambda s : s is not three_stack, + stacks + )) + to_fade.add(*filter( + lambda n : n is not numbers[3], + numbers + )) + to_fade.save_state() + rect = SurroundingRectangle( + three_stack, + color = WHITE, + stroke_width = 2 + ) + + numerator = numbers[3] + denominator = n_possibilities[-1] + frac_line = TexMobject("\\quad \\over \\quad") + for mob in numerator, denominator: + mob.generate_target() + mob.save_state() + frac = VGroup(numerator.target, frac_line, denominator.target) + frac.arrange_submobjects(DOWN, buff = SMALL_BUFF) + eq_result = TexMobject("= %.2f"%(10.0/32)) + eq_result.move_to(numerator) + eq_result.to_edge(RIGHT) + frac.next_to(eq_result, LEFT) + prob = TexMobject("P(3", "\\female", ")", "=") + prob.highlight_by_tex("female", MAROON_B) + prob.next_to(frac, LEFT) + + self.play( + ShowCreation(rect), + to_fade.fade, 0.7, + ) + self.dither() + self.play(Write(prob)) + self.dither() + self.play( + MoveToTarget(numerator), + MoveToTarget(denominator), + Write(frac_line) + ) + self.dither() + self.play(Write(eq_result)) + self.dither(2) + self.play( + numerator.restore, + denominator.restore, + to_fade.restore, + *map(FadeOut, [ + prob, frac_line, eq_result, + rect, self.title, self.n_possibilities, + ]) + ) + + def ask_about_pattern(self): + question = TextMobject("Where do these \\\\ numbers come from?") + question.to_edge(UP) + numbers = self.numbers + circles = VGroup(*[ + Circle().replace(num, dim_to_match = 1).scale_in_place(1.5) + for num in numbers + ]) + circles.highlight(WHITE) + + self.play(LaggedStart(FadeIn, question)) + self.play(LaggedStart(ShowCreationThenDestruction, circles)) + self.dither(2) + + ###### + + def get_random_lineup_of_men_and_women(self): + man, woman = Male(), Female() + lineup = self.get_lineup(*[ + woman if random.choice([True, False]) else man + for y in range(self.n_people_per_lineup) + ]) + return lineup + + def get_lineup(self, *mobjects): + lines = VGroup(*[ + Line(ORIGIN, self.item_line_width*RIGHT) + for mob in mobjects + ]) + lines.arrange_submobjects(RIGHT) + items = VGroup() + for line, mob in zip(lines, mobjects): + item = VectorizedPoint() if mob is None else mob.copy() + item.next_to(line, UP, SMALL_BUFF) + items.add(item) + result = VGroup(lines, items) + result.lines = lines + result.items = items + return result + +class RememberThisSensation(TeacherStudentsScene): + def construct(self): + self.teacher_says("Remember this \\\\ sensation") + self.change_student_modes("confused", "pondering", "erm") + self.dither(2) + + + + + + + + + + + + + + + + + + +