3b1b-manim/eop/combinations.py

845 lines
26 KiB
Python
Raw Normal View History

2017-11-29 10:11:41 -08:00
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)