mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
552 lines
17 KiB
Python
552 lines
17 KiB
Python
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 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 scene import Scene
|
|
from camera import Camera, ShadingCamera
|
|
from mobject.svg_mobject import *
|
|
from mobject.tex_mobject import *
|
|
|
|
class Jewel(VMobject):
|
|
CONFIG = {
|
|
"color" : WHITE,
|
|
"fill_opacity" : 0.75,
|
|
"stroke_width" : 0,
|
|
"propogate_style_to_family" : True,
|
|
"height" : 0.5,
|
|
"num_equator_points" : 5,
|
|
"sun_vect" : OUT+LEFT+UP,
|
|
}
|
|
def generate_points(self):
|
|
for vect in OUT, IN:
|
|
compass_vects = list(compass_directions(self.num_equator_points))
|
|
if vect is IN:
|
|
compass_vects.reverse()
|
|
for vect_pair in adjascent_pairs(compass_vects):
|
|
self.add(Polygon(vect, *vect_pair))
|
|
self.scale_to_fit_height(self.height)
|
|
self.rotate(-np.pi/2-np.pi/24, RIGHT)
|
|
self.rotate(-np.pi/12, UP)
|
|
self.submobjects.sort(lambda m1, m2 : cmp(-m1.get_center()[2], -m2.get_center()[2]))
|
|
return self
|
|
|
|
################
|
|
|
|
class CheckOutMathologer(PiCreatureScene):
|
|
CONFIG = {
|
|
"logo_height" : 1.5,
|
|
"screen_height" : 5
|
|
}
|
|
def construct(self):
|
|
logo = ImageMobject("Mathologer_logo")
|
|
logo.scale_to_fit_height(self.logo_height)
|
|
logo.to_corner(UP+LEFT)
|
|
name = TextMobject("Mathologer")
|
|
name.next_to(logo, RIGHT)
|
|
|
|
rect = Rectangle(height = 9, width = 16)
|
|
rect.scale_to_fit_height(self.screen_height)
|
|
rect.next_to(logo, DOWN)
|
|
rect.to_edge(LEFT)
|
|
|
|
logo.save_state()
|
|
logo.shift(DOWN)
|
|
logo.highlight(BLACK)
|
|
self.play(
|
|
logo.restore,
|
|
self.pi_creature.change_mode, "hooray",
|
|
)
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(name)
|
|
)
|
|
self.dither(2)
|
|
self.change_mode("happy")
|
|
self.dither(2)
|
|
|
|
class IntroduceStolenNecklaceProblem(Scene):
|
|
CONFIG = {
|
|
"camera_class" : ShadingCamera,
|
|
"jewel_colors" : [BLUE, GREEN, WHITE, RED],
|
|
"num_per_jewel" : [8, 10, 4, 6],
|
|
"num_shuffles" : 1,
|
|
"random_seed" : 2,
|
|
"forced_binary_choices" : (0, 1, 0, 1, 0),
|
|
"show_matching_after_divvying" : True,
|
|
}
|
|
def construct(self):
|
|
random.seed(self.random_seed)
|
|
self.add_thieves()
|
|
self.write_title()
|
|
self.introduce_necklace()
|
|
self.divvy_by_cutting_all()
|
|
self.divvy_with_n_cuts()
|
|
self.shuffle_jewels(self.necklace.jewels)
|
|
self.divvy_with_n_cuts()
|
|
|
|
def add_thieves(self):
|
|
thieves = VGroup(
|
|
Randolph(),
|
|
Mortimer()
|
|
)
|
|
thieves.arrange_submobjects(RIGHT, buff = 4*LARGE_BUFF)
|
|
thieves.to_edge(DOWN)
|
|
thieves[0].make_eye_contact(thieves[1])
|
|
|
|
self.add(thieves)
|
|
self.thieves = thieves
|
|
|
|
def write_title(self):
|
|
title = TextMobject("Stolen necklace problem")
|
|
title.to_edge(UP)
|
|
self.play(
|
|
Write(title),
|
|
*[
|
|
ApplyMethod(pi.look_at, title)
|
|
for pi in self.thieves
|
|
]
|
|
)
|
|
self.title = title
|
|
|
|
def introduce_necklace(self):
|
|
necklace = self.get_necklace()
|
|
jewels = necklace.jewels
|
|
jewel_types = self.get_jewels_organized_by_type(jewels)
|
|
|
|
enumeration_labels = VGroup()
|
|
for jewel_type in jewel_types:
|
|
num_mob = TexMobject(str(len(jewel_type)))
|
|
jewel_copy = jewel_type[0].copy().scale(2)
|
|
jewel_copy.next_to(num_mob)
|
|
label = VGroup(num_mob, jewel_copy)
|
|
enumeration_labels.add(label)
|
|
enumeration_labels.arrange_submobjects(RIGHT, buff = LARGE_BUFF)
|
|
enumeration_labels.to_edge(UP)
|
|
|
|
self.play(
|
|
FadeIn(
|
|
necklace,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 3
|
|
),
|
|
*it.chain(*[
|
|
[pi.change_mode, "conniving", pi.look_at, necklace]
|
|
for pi in self.thieves
|
|
])
|
|
)
|
|
self.play(*[
|
|
ApplyMethod(
|
|
jewel.rotate_in_place, np.pi/6, UP,
|
|
rate_func = there_and_back
|
|
)
|
|
for jewel in jewels
|
|
])
|
|
self.play(Blink(self.thieves[0]))
|
|
self.dither()
|
|
for x in range(self.num_shuffles):
|
|
self.shuffle_jewels(jewels)
|
|
self.play(FadeOut(self.title))
|
|
for jewel_type, label in zip(jewel_types, enumeration_labels):
|
|
jewel_type.submobjects.sort(lambda m1, m2: cmp(m1.get_center()[0], m2.get_center()[0]))
|
|
jewel_type.save_state()
|
|
jewel_type.generate_target()
|
|
jewel_type.target.arrange_submobjects()
|
|
jewel_type.target.scale(2)
|
|
jewel_type.target.move_to(2*UP)
|
|
self.play(
|
|
MoveToTarget(jewel_type),
|
|
Write(label)
|
|
)
|
|
self.play(jewel_type.restore)
|
|
self.play(Blink(self.thieves[1]))
|
|
|
|
self.enumeration_labels = enumeration_labels
|
|
self.jewel_types = jewel_types
|
|
|
|
def divvy_by_cutting_all(self):
|
|
enumeration_labels = self.enumeration_labels
|
|
necklace = self.necklace
|
|
jewel_types = self.jewel_types
|
|
thieves = self.thieves
|
|
|
|
both_half_labels = VGroup()
|
|
for thief, vect in zip(self.thieves, [LEFT, RIGHT]):
|
|
half_labels = VGroup()
|
|
for label in enumeration_labels:
|
|
tex, jewel = label
|
|
num = int(tex.get_tex_string())
|
|
half_label = VGroup(
|
|
TexMobject(str(num/2)),
|
|
jewel.copy()
|
|
)
|
|
half_label.arrange_submobjects()
|
|
half_labels.add(half_label)
|
|
half_labels.arrange_submobjects(DOWN)
|
|
half_labels.scale_to_fit_height(thief.get_height())
|
|
half_labels.next_to(
|
|
thief, vect,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = DOWN
|
|
)
|
|
both_half_labels.add(half_labels)
|
|
|
|
for half_labels in both_half_labels:
|
|
self.play(ReplacementTransform(
|
|
enumeration_labels.copy(),
|
|
half_labels
|
|
))
|
|
self.play(*[ApplyMethod(pi.change_mode, "pondering") for pi in thieves])
|
|
self.dither()
|
|
|
|
for type_index, jewel_type in enumerate(jewel_types):
|
|
jewel_type.save_state()
|
|
jewel_type_copy = jewel_type.copy()
|
|
n_jewels = len(jewel_type)
|
|
halves = [
|
|
VGroup(*jewel_type_copy[:n_jewels/2]),
|
|
VGroup(*jewel_type_copy[n_jewels/2:]),
|
|
]
|
|
for half, thief, vect in zip(halves, thieves, [RIGHT, LEFT]):
|
|
half.arrange_submobjects(DOWN)
|
|
half.next_to(
|
|
thief, vect,
|
|
buff = SMALL_BUFF + type_index*half.get_width(),
|
|
aligned_edge = DOWN
|
|
)
|
|
self.play(
|
|
Transform(jewel_type, jewel_type_copy),
|
|
*[
|
|
ApplyMethod(thief.look_at, jewel_type_copy)
|
|
for thief in thieves
|
|
]
|
|
)
|
|
self.play(*it.chain(*[
|
|
[thief.change_mode, "happy", thief.look_at, necklace]
|
|
for thief in thieves
|
|
]))
|
|
self.dither()
|
|
self.play(*[
|
|
jewel_type.restore
|
|
for jewel_type in jewel_types
|
|
])
|
|
self.play(*it.chain(*[
|
|
[thief.change_mode, "confused", thief.look_at, necklace]
|
|
for thief in thieves
|
|
]))
|
|
|
|
def divvy_with_n_cuts(self):
|
|
necklace = self.necklace
|
|
jewel_types = self.jewel_types
|
|
thieves = self.thieves
|
|
jewels = sorted(
|
|
necklace.jewels,
|
|
lambda m1, m2 : cmp(m1.get_center()[0], m2.get_center()[0])
|
|
)
|
|
slice_indices, binary_choices = self.find_slice_indices(jewels, jewel_types)
|
|
subgroups = [
|
|
VGroup(*jewels[i1:i2])
|
|
for i1, i2 in zip(slice_indices, slice_indices[1:])
|
|
]
|
|
buff = (jewels[1].get_left()[0]-jewels[0].get_right()[0])/2
|
|
v_lines = VGroup(*[
|
|
DashedLine(UP, DOWN).next_to(group, RIGHT, buff = buff)
|
|
for group in subgroups[:-1]
|
|
])
|
|
strand_groups = [VGroup(), VGroup()]
|
|
for group, choice in zip(subgroups, binary_choices):
|
|
strand = Line(
|
|
group[0].get_center(), group[-1].get_center(),
|
|
color = necklace.line.get_color()
|
|
)
|
|
strand.add(*group)
|
|
strand_groups[choice].add(strand)
|
|
self.add(strand)
|
|
|
|
self.play(ShowCreation(v_lines))
|
|
self.play(
|
|
FadeOut(necklace.line),
|
|
*it.chain(*[
|
|
map(Animation, group)
|
|
for group in strand_groups
|
|
])
|
|
)
|
|
for group in strand_groups:
|
|
group.save_state()
|
|
self.play(
|
|
strand_groups[0].shift, UP/2.,
|
|
strand_groups[1].shift, DOWN/2.,
|
|
)
|
|
self.play(*it.chain(*[
|
|
[thief.change_mode, "happy", thief.look_at, self.necklace]
|
|
for thief in thieves
|
|
]))
|
|
self.play(Blink(thieves[1]))
|
|
|
|
for group in strand_groups:
|
|
box = Rectangle(
|
|
width = group.get_width()+2*SMALL_BUFF,
|
|
height = group.get_height()+2*SMALL_BUFF,
|
|
stroke_width = 0,
|
|
fill_color = YELLOW,
|
|
fill_opacity = 0.3,
|
|
)
|
|
box.move_to(group)
|
|
self.play(FadeIn(box))
|
|
self.dither()
|
|
self.play(FadeOut(box))
|
|
|
|
self.dither()
|
|
if self.show_matching_after_divvying:
|
|
for jewel_type in jewel_types:
|
|
self.play(
|
|
*it.chain(*[
|
|
[
|
|
jewel.scale_in_place, 2,
|
|
jewel.rotate_in_place, np.pi/12, UP,
|
|
]
|
|
for jewel in jewel_type
|
|
]),
|
|
rate_func = there_and_back,
|
|
run_time = 2
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
FadeOut(v_lines),
|
|
FadeIn(necklace.line),
|
|
*[
|
|
group.restore for group in strand_groups
|
|
]
|
|
)
|
|
self.remove(*strand_groups)
|
|
self.add(necklace)
|
|
|
|
########
|
|
|
|
def get_necklace(self):
|
|
colors = reduce(op.add, [
|
|
num*[color]
|
|
for num, color in zip(self.num_per_jewel, self.jewel_colors)
|
|
])
|
|
jewels = VGroup(*[
|
|
Jewel(color = color)
|
|
for color in colors
|
|
])
|
|
jewels.arrange_submobjects()
|
|
jewels.scale_to_fit_width(2*SPACE_WIDTH-1)
|
|
jewels.center().shift(UP)
|
|
|
|
necklace = VGroup()
|
|
necklace.line = Line(
|
|
jewels[0].get_center(),
|
|
jewels[-1].get_center(),
|
|
color = GREY
|
|
)
|
|
necklace.jewels = jewels
|
|
necklace.add(necklace.line, *jewels)
|
|
|
|
self.necklace = necklace
|
|
return necklace
|
|
|
|
def get_jewels_organized_by_type(self, jewels):
|
|
return [
|
|
VGroup(*filter(lambda m : m.get_color() == color, jewels))
|
|
for color in map(Color, self.jewel_colors)
|
|
]
|
|
|
|
def shuffle_jewels(self, jewels, run_time = 2, path_arc = np.pi/2, **kwargs):
|
|
shuffled_indices = range(len(jewels))
|
|
random.shuffle(shuffled_indices)
|
|
target_group = VGroup(*[
|
|
jewel.copy().move_to(jewels[shuffled_indices[i]])
|
|
for i, jewel in enumerate(jewels)
|
|
])
|
|
self.play(Transform(
|
|
jewels, target_group,
|
|
run_time = run_time,
|
|
path_arc = path_arc,
|
|
**kwargs
|
|
))
|
|
|
|
def find_slice_indices(self, jewels, jewel_types):
|
|
|
|
def jewel_to_type_number(jewel):
|
|
for i, jewel_type in enumerate(jewel_types):
|
|
if jewel in jewel_type:
|
|
return i
|
|
raise Exception("Not in any jewel_types")
|
|
type_numbers = map(jewel_to_type_number, jewels)
|
|
|
|
n_types = len(jewel_types)
|
|
for slice_indices in it.combinations(range(1, len(jewels)), n_types):
|
|
slice_indices = [0] + list(slice_indices) + [len(jewels)]
|
|
if self.forced_binary_choices is not None:
|
|
all_binary_choices = [self.forced_binary_choices]
|
|
else:
|
|
all_binary_choices = it.product(*[range(2)]*(n_types+1))
|
|
for binary_choices in all_binary_choices:
|
|
subsets = [
|
|
type_numbers[i1:i2]
|
|
for i1, i2 in zip(slice_indices, slice_indices[1:])
|
|
]
|
|
left_sets, right_sets = [
|
|
[
|
|
subset
|
|
for subset, index in zip(subsets, binary_choices)
|
|
if index == target_index
|
|
]
|
|
for target_index in range(2)
|
|
]
|
|
flat_left_set = np.array(list(it.chain(*left_sets)))
|
|
flat_right_set = np.array(list(it.chain(*right_sets)))
|
|
|
|
|
|
match_array = [
|
|
sum(flat_left_set == n) == sum(flat_right_set == n)
|
|
for n in range(n_types)
|
|
]
|
|
if np.all(match_array):
|
|
return slice_indices, binary_choices
|
|
raise Exception("No fair division found")
|
|
|
|
class FiveJewelCase(IntroduceStolenNecklaceProblem):
|
|
CONFIG = {
|
|
"jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW],
|
|
"num_per_jewel" : [6, 4, 4, 2, 8],
|
|
"forced_binary_choices" : (0, 1, 0, 1, 0, 1),
|
|
}
|
|
def construct(self):
|
|
random.seed(self.random_seed)
|
|
self.add(self.get_necklace())
|
|
jewels = self.necklace.jewels
|
|
self.shuffle_jewels(jewels, run_time = 0)
|
|
self.jewel_types = self.get_jewels_organized_by_type(jewels)
|
|
self.add_title()
|
|
self.add_thieves()
|
|
for thief in self.thieves:
|
|
ApplyMethod(thief.change_mode, "pondering").update(1)
|
|
thief.look_at(self.necklace)
|
|
self.divvy_with_n_cuts()
|
|
|
|
def add_title(self):
|
|
n_cuts = len(self.jewel_colors)
|
|
title = TextMobject(
|
|
"%d jewel types, %d cuts"%(n_cuts, n_cuts)
|
|
)
|
|
title.to_edge(UP)
|
|
self.add(title)
|
|
|
|
class SixJewelCase(FiveJewelCase):
|
|
CONFIG = {
|
|
"jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW, MAROON_B],
|
|
"num_per_jewel" : [6, 4, 4, 2, 2, 6],
|
|
"forced_binary_choices" : (0, 1, 0, 1, 0, 1, 0),
|
|
}
|
|
|
|
class DiscussApplicability(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("""
|
|
Minize sharding,
|
|
allocate resources evenly
|
|
""")
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.dither(2)
|
|
|
|
class ThreeJewelCase(FiveJewelCase):
|
|
CONFIG = {
|
|
"jewel_colors" : [BLUE, GREEN, WHITE],
|
|
"num_per_jewel" : [6, 4, 8],
|
|
"forced_binary_choices" : (0, 1, 0, 1),
|
|
}
|
|
|
|
class RepeatedShuffling(IntroduceStolenNecklaceProblem):
|
|
CONFIG = {
|
|
"num_shuffles" : 5,
|
|
"random_seed" : 3,
|
|
"show_matching_after_divvying" : False,
|
|
}
|
|
def construct(self):
|
|
random.seed(self.random_seed)
|
|
self.add(self.get_necklace())
|
|
jewels = self.necklace.jewels
|
|
self.jewel_types = self.get_jewels_organized_by_type(jewels)
|
|
self.add_thieves()
|
|
for thief in self.thieves:
|
|
ApplyMethod(thief.change_mode, "pondering").update(1)
|
|
thief.look_at(self.necklace)
|
|
|
|
for x in range(self.num_shuffles):
|
|
self.shuffle_jewels(jewels)
|
|
self.divvy_with_n_cuts()
|
|
|
|
class NowForTheTopology(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("Now for the \\\\ topology")
|
|
self.change_student_modes(*["hooray"]*3)
|
|
self.dither(3)
|
|
|
|
class ExternallyAnimatedScene(Scene):
|
|
def construct(self):
|
|
raise Exception("Don't actually run this class.")
|
|
|
|
class SphereOntoPlaneIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class DiscontinuousSphereOntoPlaneIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class WriteNotAllowed(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Not allowed")
|
|
words.highlight(RED)
|
|
words.scale(2)
|
|
self.play(Write(words))
|
|
self.dither(2)
|
|
|
|
class ManyPointsLandingOnEachOtherIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|