mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
2656 lines
82 KiB
Python
2656 lines
82 KiB
Python
from manimlib.imports import *
|
|
from functools import reduce
|
|
|
|
class Jewel(VMobject):
|
|
CONFIG = {
|
|
"color" : WHITE,
|
|
"fill_opacity" : 0.75,
|
|
"stroke_width" : 0,
|
|
"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 adjacent_pairs(compass_vects):
|
|
self.add(Polygon(vect, *vect_pair))
|
|
self.set_height(self.height)
|
|
self.rotate(-np.pi/2-np.pi/24, RIGHT)
|
|
self.rotate(-np.pi/12, UP)
|
|
self.submobjects.sort(
|
|
key=lambda m: -m1.get_center()[2]
|
|
)
|
|
return self
|
|
|
|
class Necklace(VMobject):
|
|
CONFIG = {
|
|
"width" : FRAME_WIDTH - 1,
|
|
"jewel_buff" : MED_SMALL_BUFF,
|
|
"chain_color" : GREY,
|
|
"default_colors" : [(4, BLUE), (6, WHITE), (4, GREEN)]
|
|
}
|
|
def __init__(self, *colors, **kwargs):
|
|
digest_config(self, kwargs, locals())
|
|
if len(colors) == 0:
|
|
self.colors = self.get_default_colors()
|
|
VMobject.__init__(self, **kwargs)
|
|
|
|
def get_default_colors(self):
|
|
result = list(it.chain(*[
|
|
num*[color]
|
|
for num, color in self.default_colors
|
|
]))
|
|
random.shuffle(result)
|
|
return result
|
|
|
|
def generate_points(self):
|
|
jewels = VGroup(*[
|
|
Jewel(color = color)
|
|
for color in self.colors
|
|
])
|
|
jewels.arrange(buff = self.jewel_buff)
|
|
jewels.set_width(self.width)
|
|
jewels.center()
|
|
j_to_j_dist = (jewels[1].get_center()-jewels[0].get_center())[0]
|
|
|
|
chain = Line(
|
|
jewels[0].get_center() + j_to_j_dist*LEFT/2,
|
|
jewels[-1].get_center() + j_to_j_dist*RIGHT/2,
|
|
color = self.chain_color,
|
|
)
|
|
self.add(chain, *jewels)
|
|
self.chain = chain
|
|
self.jewels = jewels
|
|
|
|
################
|
|
|
|
class FromPreviousTopologyVideo(Scene):
|
|
def construct(self):
|
|
rect = Rectangle(height = 9, width = 16)
|
|
rect.set_height(FRAME_HEIGHT-2)
|
|
title = TextMobject("From original ``Who cares about topology'' video")
|
|
title.to_edge(UP)
|
|
rect.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
|
|
class CheckOutMathologer(PiCreatureScene):
|
|
CONFIG = {
|
|
"logo_height" : 1.5,
|
|
"screen_height" : 5,
|
|
"channel_name" : "Mathologer",
|
|
"logo_file" : "mathologer_logo",
|
|
"logo_color" : None,
|
|
}
|
|
def construct(self):
|
|
logo = self.get_logo()
|
|
name = TextMobject(self.channel_name)
|
|
name.next_to(logo, RIGHT)
|
|
|
|
rect = Rectangle(height = 9, width = 16)
|
|
rect.set_height(self.screen_height)
|
|
rect.next_to(logo, DOWN)
|
|
rect.to_edge(LEFT)
|
|
|
|
self.play(
|
|
self.get_logo_intro_animation(logo),
|
|
self.pi_creature.change_mode, "hooray",
|
|
)
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(name)
|
|
)
|
|
self.wait(2)
|
|
self.change_mode("happy")
|
|
self.wait(2)
|
|
|
|
def get_logo(self):
|
|
logo = ImageMobject(self.logo_file)
|
|
logo.set_height(self.logo_height)
|
|
logo.to_corner(UP+LEFT)
|
|
if self.logo_color is not None:
|
|
logo.set_color(self.logo_color)
|
|
logo.stroke_width = 1
|
|
return logo
|
|
|
|
def get_logo_intro_animation(self, logo):
|
|
logo.save_state()
|
|
logo.shift(DOWN)
|
|
logo.set_color(BLACK)
|
|
return ApplyMethod(logo.restore)
|
|
|
|
class IntroduceStolenNecklaceProblem(ThreeDScene):
|
|
CONFIG = {
|
|
"jewel_colors" : [BLUE, GREEN, WHITE, RED],
|
|
"num_per_jewel" : [8, 10, 4, 6],
|
|
"num_shuffles" : 1,
|
|
"necklace_center" : UP,
|
|
"random_seed" : 2,
|
|
"forced_binary_choices" : (0, 1, 0, 1, 0),
|
|
}
|
|
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(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(RIGHT, buff = LARGE_BUFF)
|
|
enumeration_labels.to_edge(UP)
|
|
|
|
self.play(
|
|
FadeIn(
|
|
necklace,
|
|
lag_ratio = 0.5,
|
|
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]))
|
|
for jewel_type in jewel_types:
|
|
self.play(
|
|
jewel_type.shift, 0.2*UP,
|
|
rate_func = wiggle
|
|
)
|
|
self.wait()
|
|
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(
|
|
key=lambda m: m1.get
|
|
)
|
|
jewel_type.save_state()
|
|
jewel_type.generate_target()
|
|
jewel_type.target.arrange()
|
|
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()
|
|
half_labels.add(half_label)
|
|
half_labels.arrange(DOWN)
|
|
half_labels.set_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.wait()
|
|
|
|
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(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.wait()
|
|
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,
|
|
with_thieves = True,
|
|
highlight_groups = True,
|
|
show_matching_after_divvying = True,
|
|
):
|
|
necklace = self.necklace
|
|
jewel_types = self.jewel_types
|
|
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.chain.get_color()
|
|
)
|
|
strand.add(*group)
|
|
strand_groups[choice].add(strand)
|
|
self.add(strand)
|
|
|
|
self.play(ShowCreation(v_lines))
|
|
self.play(
|
|
FadeOut(necklace.chain),
|
|
*it.chain(*[
|
|
list(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.,
|
|
)
|
|
if with_thieves:
|
|
self.play(*it.chain(*[
|
|
[thief.change_mode, "happy", thief.look_at, self.necklace]
|
|
for thief in self.thieves
|
|
]))
|
|
self.play(Blink(self.thieves[1]))
|
|
else:
|
|
self.wait()
|
|
|
|
if highlight_groups:
|
|
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.wait()
|
|
self.play(FadeOut(box))
|
|
|
|
self.wait()
|
|
if show_matching_after_divvying:
|
|
for jewel_type in jewel_types:
|
|
self.play(
|
|
*[
|
|
ApplyMethod(jewel.scale_in_place, 1.5)
|
|
for jewel in jewel_type
|
|
],
|
|
rate_func = there_and_back,
|
|
run_time = 2
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(v_lines),
|
|
FadeIn(necklace.chain),
|
|
*[
|
|
group.restore for group in strand_groups
|
|
]
|
|
)
|
|
self.remove(*strand_groups)
|
|
self.add(necklace)
|
|
|
|
########
|
|
|
|
def get_necklace(self, **kwargs):
|
|
colors = reduce(op.add, [
|
|
num*[color]
|
|
for num, color in zip(self.num_per_jewel, self.jewel_colors)
|
|
])
|
|
self.necklace = Necklace(*colors, **kwargs)
|
|
self.necklace.shift(self.necklace_center)
|
|
return self.necklace
|
|
|
|
def get_jewels_organized_by_type(self, jewels):
|
|
return [
|
|
VGroup(*[m for m in jewels if m.get_color() == color])
|
|
for color in map(Color, self.jewel_colors)
|
|
]
|
|
|
|
def shuffle_jewels(self, jewels, run_time = 2, path_arc = np.pi/2, **kwargs):
|
|
shuffled_indices = list(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 = list(map(jewel_to_type_number, jewels))
|
|
|
|
n_types = len(jewel_types)
|
|
for slice_indices in it.combinations(list(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(*[list(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 ThingToProve(PiCreatureScene):
|
|
def construct(self):
|
|
arrow = Arrow(UP, DOWN)
|
|
top_words = TextMobject("$n$ jewel types")
|
|
top_words.next_to(arrow, UP)
|
|
bottom_words = TextMobject("""
|
|
Fair division possible
|
|
with $n$ cuts
|
|
""")
|
|
bottom_words.next_to(arrow, DOWN)
|
|
|
|
self.play(
|
|
Write(top_words, run_time = 2),
|
|
self.pi_creature.change_mode, "raise_right_hand"
|
|
)
|
|
self.play(ShowCreation(arrow))
|
|
self.play(
|
|
Write(bottom_words, run_time = 2),
|
|
self.pi_creature.change_mode, "pondering"
|
|
)
|
|
self.wait(3)
|
|
|
|
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("""
|
|
Minimize sharding,
|
|
allocate resources evenly
|
|
""")
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.wait(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(
|
|
show_matching_after_divvying = False
|
|
)
|
|
|
|
class NowForTheTopology(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("Now for the \\\\ topology")
|
|
self.change_student_modes(*["hooray"]*3)
|
|
self.wait(3)
|
|
|
|
class ExternallyAnimatedScene(Scene):
|
|
def construct(self):
|
|
raise Exception("Don't actually run this class.")
|
|
|
|
class SphereOntoPlaneIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class DiscontinuousSphereOntoPlaneIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class WriteWords(Scene):
|
|
CONFIG = {
|
|
"words" : "",
|
|
"color" : WHITE,
|
|
}
|
|
def construct(self):
|
|
words = TextMobject(self.words)
|
|
words.set_color(self.color)
|
|
words.set_width(FRAME_WIDTH-1)
|
|
words.to_edge(DOWN)
|
|
self.play(Write(words))
|
|
self.wait(2)
|
|
|
|
class WriteNotAllowed(WriteWords):
|
|
CONFIG = {
|
|
"words" : "Not allowed",
|
|
"color" : RED,
|
|
}
|
|
|
|
class NonAntipodalCollisionIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class AntipodalCollisionIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class WriteBorsukUlam(WriteWords):
|
|
CONFIG = {
|
|
"words" : "Borsuk-Ulam Theorem",
|
|
}
|
|
|
|
class WriteAntipodal(WriteWords):
|
|
CONFIG = {
|
|
"words" : "``Antipodal''",
|
|
"color" : MAROON_B,
|
|
}
|
|
|
|
class ProjectOntoEquatorIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ProjectOntoEquatorWithPolesIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ProjectAntipodalNonCollisionIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ShearThenProjectnOntoEquatorPolesMissIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ShearThenProjectnOntoEquatorAntipodalCollisionIn3D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ClassicExample(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("The classic example...")
|
|
self.change_student_modes(*["happy"]*3)
|
|
self.wait(2)
|
|
|
|
class AntipodalEarthPoints(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class RotatingEarth(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class TemperaturePressurePlane(GraphScene):
|
|
CONFIG = {
|
|
"x_labeled_nums" : [],
|
|
"y_labeled_nums" : [],
|
|
"x_axis_label" : "Temperature",
|
|
"y_axis_label" : "Pressure",
|
|
"graph_origin" : 2.5*DOWN + 2*LEFT,
|
|
"corner_square_width" : 4,
|
|
"example_point_coords" : (2, 5),
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.draw_arrow()
|
|
self.add_example_coordinates()
|
|
self.wander_continuously()
|
|
|
|
def draw_arrow(self):
|
|
square = Square(
|
|
side_length = self.corner_square_width,
|
|
stroke_color = WHITE,
|
|
stroke_width = 0,
|
|
)
|
|
square.to_corner(UP+LEFT, buff = 0)
|
|
|
|
arrow = Arrow(
|
|
square.get_right(),
|
|
self.coords_to_point(*self.example_point_coords)
|
|
)
|
|
|
|
self.play(ShowCreation(arrow))
|
|
|
|
def add_example_coordinates(self):
|
|
dot = Dot(self.coords_to_point(*self.example_point_coords))
|
|
dot.set_color(YELLOW)
|
|
tex = TexMobject("(25^\\circ\\text{C}, 101 \\text{ kPa})")
|
|
tex.next_to(dot, UP+RIGHT, buff = SMALL_BUFF)
|
|
|
|
self.play(ShowCreation(dot))
|
|
self.play(Write(tex))
|
|
self.wait()
|
|
self.play(FadeOut(tex))
|
|
|
|
def wander_continuously(self):
|
|
path = VMobject().set_points_smoothly([
|
|
ORIGIN, 2*UP+RIGHT, 2*DOWN+RIGHT,
|
|
5*RIGHT, 4*RIGHT+UP, 3*RIGHT+2*DOWN,
|
|
DOWN+LEFT, 2*RIGHT
|
|
])
|
|
point = self.coords_to_point(*self.example_point_coords)
|
|
path.shift(point)
|
|
|
|
path.set_color(GREEN)
|
|
|
|
self.play(ShowCreation(path, run_time = 10, rate_func=linear))
|
|
self.wait()
|
|
|
|
class AlternateSphereSquishing(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class AlternateAntipodalCollision(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class AskWhy(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says("But...why?")
|
|
self.change_student_modes("pondering", None, "thinking")
|
|
self.play(self.get_teacher().change_mode, "happy")
|
|
self.wait(3)
|
|
|
|
class PointOutVSauce(CheckOutMathologer):
|
|
CONFIG = {
|
|
"channel_name" : "",
|
|
"logo_file" : "Vsauce_logo",
|
|
"logo_height" : 1,
|
|
"logo_color" : GREY,
|
|
}
|
|
def get_logo(self):
|
|
logo = SVGMobject(file_name = self.logo_file)
|
|
logo.set_height(self.logo_height)
|
|
logo.to_corner(UP+LEFT)
|
|
logo.set_stroke(width = 0)
|
|
logo.set_fill(GREEN)
|
|
logo.sort()
|
|
return logo
|
|
|
|
def get_logo_intro_animation(self, logo):
|
|
return DrawBorderThenFill(
|
|
logo,
|
|
run_time = 2,
|
|
)
|
|
|
|
class WalkEquatorPostTransform(GraphScene):
|
|
CONFIG = {
|
|
"x_labeled_nums" : [],
|
|
"y_labeled_nums" : [],
|
|
"graph_origin" : 2.5*DOWN + 2*LEFT,
|
|
"curved_arrow_color" : WHITE,
|
|
"curved_arrow_radius" : 3,
|
|
"num_great_arcs" : 10,
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_curved_arrow()
|
|
self.great_arc_images = self.get_great_arc_images()
|
|
|
|
self.walk_equator()
|
|
self.walk_tilted_equator()
|
|
self.draw_transverse_curve()
|
|
self.walk_transverse_curve()
|
|
|
|
def add_curved_arrow(self):
|
|
arc = Arc(
|
|
start_angle = 2*np.pi/3, angle = -np.pi/2,
|
|
radius = self.curved_arrow_radius,
|
|
color = self.curved_arrow_color
|
|
)
|
|
arc.add_tip()
|
|
arc.move_to(self.coords_to_point(0, 7))
|
|
|
|
self.add(arc)
|
|
|
|
def walk_equator(self):
|
|
equator = self.great_arc_images[0]
|
|
dots = VGroup(Dot(), Dot())
|
|
dots.set_color(MAROON_B)
|
|
dot_movement = self.get_arc_walk_dot_movement(equator, dots)
|
|
dot_movement.update(0)
|
|
|
|
self.play(ShowCreation(equator, run_time = 3))
|
|
self.play(FadeIn(dots[0]))
|
|
dots[1].set_fill(opacity = 0)
|
|
self.play(dot_movement)
|
|
self.play(dots[1].set_fill, None, 1)
|
|
self.play(dot_movement)
|
|
self.play(dot_movement)
|
|
|
|
proportion = equator.collision_point_proportion
|
|
self.play(self.get_arc_walk_dot_movement(
|
|
equator, dots,
|
|
rate_func = lambda t : 2*proportion*smooth(t)
|
|
))
|
|
v_line = DashedLine(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN)
|
|
v_line.shift(dots.get_center()[0]*RIGHT)
|
|
self.play(ShowCreation(v_line))
|
|
self.wait()
|
|
self.play(FadeOut(v_line))
|
|
|
|
dots.save_state()
|
|
equator.save_state()
|
|
self.play(
|
|
equator.fade,
|
|
dots.fade
|
|
)
|
|
|
|
self.first_dots = dots
|
|
|
|
def walk_tilted_equator(self):
|
|
equator = self.great_arc_images[0]
|
|
tilted_eq = self.great_arc_images[1]
|
|
|
|
dots = VGroup(Dot(), Dot())
|
|
dots.set_color(MAROON_B)
|
|
dot_movement = self.get_arc_walk_dot_movement(tilted_eq, dots)
|
|
dot_movement.update(0)
|
|
|
|
self.play(ReplacementTransform(equator.copy(), tilted_eq))
|
|
self.wait()
|
|
self.play(FadeIn(dots))
|
|
self.play(dot_movement)
|
|
|
|
proportion = tilted_eq.collision_point_proportion
|
|
self.play(self.get_arc_walk_dot_movement(
|
|
tilted_eq, dots,
|
|
rate_func = lambda t : 2*proportion*smooth(t)
|
|
))
|
|
v_line = DashedLine(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN)
|
|
v_line.shift(dots.get_center()[0]*RIGHT)
|
|
self.play(ShowCreation(v_line))
|
|
self.wait()
|
|
self.play(FadeOut(v_line))
|
|
self.play(*list(map(FadeOut, [tilted_eq, dots])))
|
|
|
|
def draw_transverse_curve(self):
|
|
transverse_curve = self.get_transverse_curve(self.great_arc_images)
|
|
dots = self.first_dots
|
|
equator = self.great_arc_images[0]
|
|
|
|
self.play(dots.restore)
|
|
equator.restore()
|
|
self.great_arc_images.fade()
|
|
|
|
target_arcs = list(self.great_arc_images[1:])
|
|
target_dots = []
|
|
for arc in target_arcs:
|
|
new_dots = dots.copy()
|
|
for dot, point in zip(new_dots, arc.x_collision_points):
|
|
dot.move_to(point)
|
|
target_dots.append(new_dots)
|
|
|
|
alt_eq = equator.copy()
|
|
alt_eq.points = np.array(list(reversed(alt_eq.points)))
|
|
alt_dots = dots.copy()
|
|
alt_dots.submobjects.reverse()
|
|
target_arcs += [alt_eq, alt_eq.copy()]
|
|
target_dots += [alt_dots, alt_dots.copy()]
|
|
|
|
equator_transform = Succession(*[
|
|
Transform(equator, arc, rate_func=linear)
|
|
for arc in target_arcs
|
|
])
|
|
dots_transform = Succession(*[
|
|
Transform(dots, target, rate_func=linear)
|
|
for target in target_dots
|
|
])
|
|
|
|
self.play(
|
|
ShowCreation(transverse_curve, lag_ratio = 0),
|
|
equator_transform,
|
|
dots_transform,
|
|
run_time = 10,
|
|
rate_func=linear,
|
|
)
|
|
self.wait(2)
|
|
|
|
def walk_transverse_curve(self):
|
|
transverse_curve = self.get_transverse_curve(self.great_arc_images)
|
|
dots = self.first_dots
|
|
|
|
def dot_update(dots, alpha):
|
|
for dot, curve in zip(dots, transverse_curve):
|
|
dot.move_to(curve.point_from_proportion(alpha))
|
|
return dots
|
|
|
|
for x in range(2):
|
|
self.play(
|
|
UpdateFromAlphaFunc(dots, dot_update),
|
|
run_time = 4
|
|
)
|
|
self.play(
|
|
UpdateFromAlphaFunc(dots, dot_update),
|
|
run_time = 4,
|
|
rate_func = lambda t : 0.455*smooth(t)
|
|
)
|
|
self.play(
|
|
dots.set_color, YELLOW,
|
|
dots.scale_in_place, 1.2,
|
|
rate_func = there_and_back
|
|
)
|
|
self.wait()
|
|
|
|
#######
|
|
|
|
def get_arc_walk_dot_movement(self, arc, dots, **kwargs):
|
|
def dot_update(dots, alpha):
|
|
dots[0].move_to(arc.point_from_proportion(0.5*alpha))
|
|
dots[1].move_to(arc.point_from_proportion(0.5+0.5*alpha))
|
|
return dots
|
|
if "run_time" not in kwargs:
|
|
kwargs["run_time"] = 5
|
|
return UpdateFromAlphaFunc(dots, dot_update, **kwargs)
|
|
|
|
def sphere_to_plane(self, point):
|
|
x, y, z = point
|
|
return np.array([
|
|
x - 2*x*z + y + 1,
|
|
y+0.5*y*np.cos(z*np.pi),
|
|
0
|
|
])
|
|
|
|
def sphere_point(self, portion_around_equator, equator_tilt = 0):
|
|
theta = portion_around_equator*2*np.pi
|
|
point = np.cos(theta)*RIGHT + np.sin(theta)*UP
|
|
phi = equator_tilt*np.pi
|
|
return rotate_vector(point, phi, RIGHT)
|
|
|
|
def get_great_arc_images(self):
|
|
curves = VGroup(*[
|
|
ParametricFunction(
|
|
lambda t : self.sphere_point(t, s)
|
|
).apply_function(self.sphere_to_plane)
|
|
for s in np.arange(0, 1, 1./self.num_great_arcs)
|
|
# for s in [0]
|
|
])
|
|
curves.set_color(YELLOW)
|
|
curves[0].set_color(RED)
|
|
for curve in curves:
|
|
antipodal_x_diff = lambda x : \
|
|
curve.point_from_proportion(x+0.5)[0]-\
|
|
curve.point_from_proportion(x)[0]
|
|
last_x = 0
|
|
last_sign = np.sign(antipodal_x_diff(last_x))
|
|
for x in np.linspace(0, 0.5, 100):
|
|
sign = np.sign(antipodal_x_diff(x))
|
|
if sign != last_sign:
|
|
mean = np.mean([last_x, x])
|
|
curve.x_collision_points = [
|
|
curve.point_from_proportion(mean),
|
|
curve.point_from_proportion(mean+0.5),
|
|
]
|
|
curve.collision_point_proportion = mean
|
|
break
|
|
last_x = x
|
|
last_sign = sign
|
|
return curves
|
|
|
|
def get_transverse_curve(self, gerat_arc_images):
|
|
points = list(it.chain(*[
|
|
[
|
|
curve.x_collision_points[i]
|
|
for curve in gerat_arc_images
|
|
]
|
|
for i in (0, 1)
|
|
]))
|
|
full_curve = VMobject(close_new_points = True)
|
|
full_curve.set_points_smoothly(points + [points[0]])
|
|
full_curve.set_color(MAROON_B)
|
|
first_half = full_curve.copy().pointwise_become_partial(
|
|
full_curve, 0, 0.5
|
|
)
|
|
second_half = first_half.copy().rotate_in_place(np.pi, RIGHT)
|
|
broken_curve = VGroup(first_half, second_half)
|
|
return broken_curve
|
|
|
|
class WalkAroundEquatorPreimage(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class WalkTiltedEquatorPreimage(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class FormLoopTransverseToEquator(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class AntipodalWalkAroundTransverseLoop(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class MentionGenerality(TeacherStudentsScene, ThreeDScene):
|
|
def construct(self):
|
|
necklace = Necklace(width = FRAME_X_RADIUS)
|
|
necklace.shift(2*UP)
|
|
necklace.to_edge(RIGHT)
|
|
arrow = TexMobject("\\Leftrightarrow")
|
|
arrow.scale(2)
|
|
arrow.next_to(necklace, LEFT)
|
|
q_marks = TexMobject("???")
|
|
q_marks.next_to(arrow, UP)
|
|
arrow.add(q_marks)
|
|
|
|
formula = TexMobject("f(\\textbf{x}) = f(-\\textbf{x})")
|
|
formula.next_to(self.get_students(), UP, buff = LARGE_BUFF)
|
|
formula.to_edge(LEFT, buff = LARGE_BUFF)
|
|
|
|
self.play(
|
|
self.teacher.change_mode, "raise_right_hand",
|
|
self.teacher.look_at, arrow
|
|
)
|
|
self.play(
|
|
FadeIn(necklace, run_time = 2, lag_ratio = 0.5),
|
|
Write(arrow),
|
|
*[
|
|
ApplyMethod(pi.look_at, arrow)
|
|
for pi in self.get_pi_creatures()
|
|
]
|
|
)
|
|
self.change_student_modes("pondering", "erm", "confused")
|
|
self.wait()
|
|
self.play(*[
|
|
ApplyMethod(pi.look_at, arrow)
|
|
for pi in self.get_pi_creatures()
|
|
])
|
|
self.play(Write(formula))
|
|
self.wait(3)
|
|
|
|
class SimpleSphere(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class PointsIn3D(Scene):
|
|
CONFIG = {
|
|
# "colors" : [RED, GREEN, BLUE],
|
|
"colors" : color_gradient([GREEN, BLUE], 3),
|
|
}
|
|
def construct(self):
|
|
sphere_def = TextMobject(
|
|
"\\doublespacing Sphere in 3D: All", "$(x_1, x_2, x_3)$\\\\",
|
|
"such that", "$x_1^2 + x_2^2 + x_3^2 = 1$",
|
|
alignment = "",
|
|
)
|
|
sphere_def.next_to(ORIGIN, DOWN)
|
|
for index, subindex_list in (1, [1, 2, 4, 5, 7, 8]), (3, [0, 2, 4, 6, 8, 10]):
|
|
colors = np.repeat(self.colors, 2)
|
|
for subindex, color in zip(subindex_list, colors):
|
|
sphere_def[index][subindex].set_color(color)
|
|
|
|
point_ex = TextMobject(
|
|
"For example, ",
|
|
"(", "0.41", ", ", "-0.58", ", ", "0.71", ")",
|
|
arg_separator = ""
|
|
)
|
|
for index, color in zip([2, 4, 6], self.colors):
|
|
point_ex[index].set_color(color)
|
|
point_ex.scale(0.8)
|
|
point_ex.next_to(
|
|
sphere_def[1], UP+RIGHT,
|
|
buff = 1.5*LARGE_BUFF
|
|
)
|
|
point_ex.shift_onto_screen()
|
|
arrow = Arrow(sphere_def[1].get_top(), point_ex.get_bottom())
|
|
|
|
self.play(Write(sphere_def[1]))
|
|
self.play(ShowCreation(arrow))
|
|
self.play(Write(point_ex))
|
|
self.wait()
|
|
self.play(
|
|
Animation(sphere_def[1].copy(), remover = True),
|
|
Write(sphere_def),
|
|
)
|
|
self.wait()
|
|
|
|
class AntipodalPairToBeGivenCoordinates(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class WritePointCoordinates(Scene):
|
|
CONFIG = {
|
|
"colors" : color_gradient([GREEN, BLUE], 3),
|
|
"corner" : DOWN+RIGHT,
|
|
}
|
|
def construct(self):
|
|
coords = self.get_coords()
|
|
arrow = Arrow(
|
|
-self.corner, self.corner,
|
|
stroke_width = 8,
|
|
color = MAROON_B
|
|
)
|
|
x_component = self.corner[0]*RIGHT
|
|
y_component = self.corner[1]*UP
|
|
arrow.next_to(
|
|
coords.get_edge_center(y_component),
|
|
y_component,
|
|
aligned_edge = -x_component,
|
|
buff = MED_SMALL_BUFF
|
|
)
|
|
|
|
group = VGroup(coords, arrow)
|
|
group.scale(2)
|
|
group.to_corner(self.corner)
|
|
|
|
|
|
self.play(FadeIn(coords))
|
|
self.play(ShowCreation(arrow))
|
|
self.wait()
|
|
|
|
def get_coords(self):
|
|
coords = TexMobject(
|
|
"(", "0.41", ", ", "-0.58", ", ", "0.71", ")",
|
|
arg_separator = ""
|
|
)
|
|
for index, color in zip([1, 3, 5], self.colors):
|
|
coords[index].set_color(color)
|
|
return coords
|
|
|
|
class WriteAntipodalCoordinates(WritePointCoordinates):
|
|
CONFIG = {
|
|
"corner" : UP+LEFT,
|
|
"sign_color" : RED,
|
|
}
|
|
|
|
def get_coords(self):
|
|
coords = TexMobject(
|
|
"(", "-", "0.41", ", ", "+", "0.58", ", ", "-", "0.71", ")",
|
|
arg_separator = ""
|
|
)
|
|
for index, color in zip([2, 5, 8], self.colors):
|
|
coords[index].set_color(color)
|
|
coords[index-1].set_color(self.sign_color)
|
|
return coords
|
|
|
|
class GeneralizeBorsukUlam(Scene):
|
|
CONFIG = {
|
|
"n_dims" : 3,
|
|
"boundary_colors" : [GREEN_B, BLUE],
|
|
"output_boundary_color" : [MAROON_B, YELLOW],
|
|
"negative_color" : RED,
|
|
}
|
|
def setup(self):
|
|
self.colors = color_gradient(self.boundary_colors, self.n_dims)
|
|
|
|
def construct(self):
|
|
sphere_set = self.get_sphere_set()
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
f = TexMobject("f")
|
|
output_space = self.get_output_space()
|
|
equation = self.get_equation()
|
|
|
|
sphere_set.to_corner(UP+LEFT)
|
|
arrow.next_to(sphere_set, RIGHT)
|
|
f.next_to(arrow, UP)
|
|
output_space.next_to(arrow, RIGHT)
|
|
equation.next_to(sphere_set, DOWN, buff = LARGE_BUFF)
|
|
equation.to_edge(RIGHT)
|
|
lhs = VGroup(*equation[:2])
|
|
eq = equation[2]
|
|
rhs = VGroup(*equation[3:])
|
|
|
|
self.play(FadeIn(sphere_set))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
Write(f)
|
|
)
|
|
self.play(Write(output_space))
|
|
self.wait()
|
|
self.play(FadeIn(lhs))
|
|
self.play(
|
|
ReplacementTransform(lhs.copy(), rhs),
|
|
Write(eq)
|
|
)
|
|
self.wait()
|
|
|
|
def get_condition(self):
|
|
squares = list(map(TexMobject, [
|
|
"x_%d^2"%d
|
|
for d in range(1, 1+self.n_dims)
|
|
]))
|
|
for square, color in zip(squares, self.colors):
|
|
square[0].set_color(color)
|
|
square[-1].set_color(color)
|
|
plusses = [TexMobject("+") for x in range(self.n_dims-1)]
|
|
plusses += [TexMobject("=1")]
|
|
condition = VGroup(*it.chain(*list(zip(squares, plusses))))
|
|
condition.arrange(RIGHT)
|
|
|
|
return condition
|
|
|
|
def get_tuple(self):
|
|
mid_parts = list(it.chain(*[
|
|
["x_%d"%d, ","]
|
|
for d in range(1, self.n_dims)
|
|
]))
|
|
tup = TexMobject(*["("] + mid_parts + ["x_%d"%self.n_dims, ")"])
|
|
for index, color in zip(it.count(1, 2), self.colors):
|
|
tup[index].set_color(color)
|
|
|
|
return tup
|
|
|
|
def get_negative_tuple(self):
|
|
mid_parts = list(it.chain(*[
|
|
["-", "x_%d"%d, ","]
|
|
for d in range(1, self.n_dims)
|
|
]))
|
|
tup = TexMobject(*["("] + mid_parts + ["-", "x_%d"%self.n_dims, ")"])
|
|
for index, color in zip(it.count(1, 3), self.colors):
|
|
tup[index].set_color(self.negative_color)
|
|
tup[index+1].set_color(color)
|
|
|
|
return tup
|
|
|
|
def get_output_space(self):
|
|
return TextMobject("%dD space"%(self.n_dims-1))
|
|
# n_dims = self.n_dims-1
|
|
# colors = color_gradient(self.output_boundary_color, n_dims)
|
|
# mid_parts = list(it.chain(*[
|
|
# ["y_%d"%d, ","]
|
|
# for d in range(1, n_dims)
|
|
# ]))
|
|
# tup = TexMobject(*["("] + mid_parts + ["y_%d"%n_dims, ")"])
|
|
# for index, color in zip(it.count(1, 2), colors):
|
|
# tup[index].set_color(color)
|
|
|
|
# return tup
|
|
|
|
def get_equation(self):
|
|
tup = self.get_tuple()
|
|
neg_tup = self.get_negative_tuple()
|
|
f1, f2 = [TexMobject("f") for x in range(2)]
|
|
equals = TexMobject("=")
|
|
equation = VGroup(f1, tup, equals, f2, neg_tup)
|
|
equation.arrange(RIGHT, buff = SMALL_BUFF)
|
|
|
|
return equation
|
|
|
|
def get_sphere_set(self):
|
|
tup = self.get_tuple()
|
|
such_that = TextMobject("such that")
|
|
such_that.next_to(tup, RIGHT)
|
|
condition = self.get_condition()
|
|
condition.next_to(
|
|
tup, DOWN,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
group = VGroup(tup, such_that, condition)
|
|
l_brace = Brace(group, LEFT)
|
|
r_brace = Brace(group, RIGHT)
|
|
group.add(l_brace, r_brace)
|
|
|
|
return group
|
|
|
|
# class FiveDBorsukUlam(GeneralizeBorsukUlam):
|
|
# CONFIG = {
|
|
# "n_dims" : 5,
|
|
# }
|
|
|
|
class MentionMakingNecklaceProblemContinuous(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("""
|
|
Translate this into
|
|
a continuous problem.
|
|
""")
|
|
self.change_student_modes("confused", "pondering", "erm")
|
|
self.wait(3)
|
|
|
|
class MakeTwoJewelCaseContinuous(IntroduceStolenNecklaceProblem):
|
|
CONFIG = {
|
|
"jewel_colors" : [BLUE, GREEN],
|
|
"num_per_jewel" : [8, 10],
|
|
"random_seed" : 2,
|
|
"forced_binary_choices" : (0, 1, 0),
|
|
"show_matching_after_divvying" : True,
|
|
"necklace_center" : ORIGIN,
|
|
"necklace_width" : FRAME_WIDTH - 3,
|
|
"random_seed" : 0,
|
|
"num_continuous_division_searches" : 4,
|
|
}
|
|
def construct(self):
|
|
random.seed(self.random_seed)
|
|
self.introduce_necklace()
|
|
self.divvy_with_n_cuts()
|
|
self.identify_necklace_with_unit_interval()
|
|
self.color_necklace()
|
|
self.find_continuous_fair_division()
|
|
self.show_continuous_fair_division()
|
|
self.set_color_continuous_groups()
|
|
self.mention_equivalence_to_discrete_case()
|
|
self.shift_divide_off_tick_marks()
|
|
|
|
def introduce_necklace(self):
|
|
self.get_necklace(
|
|
width = self.necklace_width,
|
|
)
|
|
self.play(FadeIn(
|
|
self.necklace,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.shuffle_jewels(self.necklace.jewels)
|
|
jewel_types = self.get_jewels_organized_by_type(
|
|
self.necklace.jewels
|
|
)
|
|
self.wait()
|
|
self.count_jewel_types(jewel_types)
|
|
self.wait()
|
|
|
|
self.jewel_types = jewel_types
|
|
|
|
def count_jewel_types(self, jewel_types):
|
|
enumeration_labels = VGroup()
|
|
for jewel_type in jewel_types:
|
|
num_mob = TexMobject(str(len(jewel_type)))
|
|
jewel_copy = jewel_type[0].copy()
|
|
# jewel_copy.set_height(num_mob.get_height())
|
|
jewel_copy.next_to(num_mob)
|
|
label = VGroup(num_mob, jewel_copy)
|
|
enumeration_labels.add(label)
|
|
enumeration_labels.arrange(RIGHT, buff = LARGE_BUFF)
|
|
enumeration_labels.to_edge(UP)
|
|
|
|
for jewel_type, label in zip(jewel_types, enumeration_labels):
|
|
jewel_type.sort_submobjects()
|
|
|
|
jewel_type.save_state()
|
|
jewel_type.generate_target()
|
|
jewel_type.target.arrange()
|
|
jewel_type.target.move_to(2*UP)
|
|
self.play(
|
|
MoveToTarget(jewel_type),
|
|
Write(label)
|
|
)
|
|
self.play(jewel_type.restore)
|
|
|
|
def divvy_with_n_cuts(self):
|
|
IntroduceStolenNecklaceProblem.divvy_with_n_cuts(
|
|
self,
|
|
with_thieves = False,
|
|
highlight_groups = False,
|
|
show_matching_after_divvying = True,
|
|
)
|
|
|
|
def identify_necklace_with_unit_interval(self):
|
|
interval = UnitInterval(
|
|
tick_frequency = 1./sum(self.num_per_jewel),
|
|
tick_size = 0.2,
|
|
numbers_with_elongated_ticks = [],
|
|
)
|
|
interval.stretch_to_fit_width(self.necklace.get_width())
|
|
interval.move_to(self.necklace)
|
|
tick_marks = interval.tick_marks
|
|
tick_marks.set_stroke(WHITE, width = 2)
|
|
|
|
brace = Brace(interval)
|
|
brace_text = brace.get_text("Length = 1")
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(brace_text)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(interval.tick_marks),
|
|
)
|
|
self.wait()
|
|
|
|
self.tick_marks = interval.tick_marks
|
|
self.length_brace = VGroup(brace, brace_text)
|
|
|
|
def color_necklace(self):
|
|
example_index = len(self.necklace.jewels)/2
|
|
jewels = self.necklace.jewels
|
|
chain = self.necklace.chain
|
|
self.remove(self.necklace)
|
|
self.add(chain, jewels)
|
|
|
|
jewels.submobjects.sort(
|
|
key=lambda m: m.get_center()[0]
|
|
)
|
|
remaining_indices = list(range(len(jewels)))
|
|
remaining_indices.remove(example_index)
|
|
|
|
example_segment = self.color_necklace_by_indices(example_index)
|
|
remaining_segments = self.color_necklace_by_indices(*remaining_indices)
|
|
self.remove(chain)
|
|
segments = VGroup(example_segment[0], *remaining_segments)
|
|
segments.submobjects.sort(
|
|
key=lambda m: m.get_center()[0]
|
|
)
|
|
segment_types = VGroup(*[
|
|
VGroup(*[m for m in segments if m.get_color() == Color(color)])
|
|
for color in self.jewel_colors
|
|
])
|
|
|
|
for group in segment_types:
|
|
length_tex = TexMobject("\\frac{%d}{%d}"%(
|
|
len(group),
|
|
len(jewels)
|
|
))
|
|
length_tex.next_to(group, UP)
|
|
length_tex.shift(UP)
|
|
self.play(
|
|
group.shift, UP,
|
|
Write(length_tex, run_time = 1),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
group.shift, DOWN,
|
|
FadeOut(length_tex)
|
|
)
|
|
self.play(FadeOut(self.length_brace))
|
|
|
|
self.segments = segments
|
|
|
|
def color_necklace_by_indices(self, *indices):
|
|
chain = self.necklace.chain
|
|
jewels = VGroup(*[
|
|
self.necklace.jewels[i]
|
|
for i in indices
|
|
])
|
|
n_jewels = len(self.necklace.jewels)
|
|
|
|
segments = VGroup(*[
|
|
Line(
|
|
chain.point_from_proportion(index/float(n_jewels)),
|
|
chain.point_from_proportion((index+1)/float(n_jewels)),
|
|
color = jewel.get_color()
|
|
)
|
|
for index, jewel in zip(indices, jewels)
|
|
])
|
|
for jewel in jewels:
|
|
jewel.save_state()
|
|
|
|
self.play(jewels.shift, jewels.get_height()*UP)
|
|
self.play(ReplacementTransform(
|
|
jewels, segments,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
))
|
|
self.wait()
|
|
return segments
|
|
|
|
def find_continuous_fair_division(self):
|
|
chain = self.necklace.chain
|
|
n_jewels = len(self.necklace.jewels)
|
|
|
|
slice_indices, ignore = self.find_slice_indices(
|
|
self.necklace.jewels,
|
|
self.jewel_types
|
|
)
|
|
cut_proportions = [
|
|
sorted([random.random(), random.random()])
|
|
for x in range(self.num_continuous_division_searches)
|
|
]
|
|
cut_proportions.append([
|
|
float(i)/n_jewels
|
|
for i in slice_indices[1:-1]
|
|
])
|
|
cut_points = [
|
|
list(map(chain.point_from_proportion, pair))
|
|
for pair in cut_proportions
|
|
]
|
|
v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)])
|
|
|
|
for line, point in zip(v_lines, cut_points[0]):
|
|
line.move_to(point)
|
|
|
|
self.play(ShowCreation(v_lines))
|
|
self.wait()
|
|
for target_points in cut_points[1:]:
|
|
self.play(*[
|
|
ApplyMethod(line.move_to, point)
|
|
for line, point in zip(v_lines, target_points)
|
|
])
|
|
self.wait()
|
|
|
|
self.slice_indices = slice_indices
|
|
self.v_lines = v_lines
|
|
|
|
def show_continuous_fair_division(self):
|
|
slice_indices = self.slice_indices
|
|
|
|
groups = [
|
|
VGroup(
|
|
VGroup(*self.segments[i1:i2]),
|
|
VGroup(*self.tick_marks[i1:i2]),
|
|
)
|
|
for i1, i2 in zip(slice_indices, slice_indices[1:])
|
|
]
|
|
groups[-1].add(self.tick_marks[-1])
|
|
vects = [[UP, DOWN][i] for i in self.forced_binary_choices]
|
|
|
|
self.play(*[
|
|
ApplyMethod(group.shift, 0.5*vect)
|
|
for group, vect in zip(groups, vects)
|
|
])
|
|
self.wait()
|
|
|
|
self.groups = groups
|
|
|
|
def set_color_continuous_groups(self):
|
|
top_group = VGroup(self.groups[0], self.groups[2])
|
|
bottom_group = self.groups[1]
|
|
boxes = VGroup()
|
|
for group in top_group, bottom_group:
|
|
box = Rectangle(
|
|
width = FRAME_WIDTH-2,
|
|
height = group.get_height()+SMALL_BUFF,
|
|
stroke_width = 0,
|
|
fill_color = WHITE,
|
|
fill_opacity = 0.25,
|
|
)
|
|
box.shift(group.get_center()[1]*UP)
|
|
boxes.add(box)
|
|
|
|
weight_description = VGroup(*[
|
|
VGroup(
|
|
TexMobject("\\frac{%d}{%d}"%(
|
|
len(jewel_type)/2, len(self.segments)
|
|
)),
|
|
Jewel(color = jewel_type[0].get_color())
|
|
).arrange()
|
|
for jewel_type in self.jewel_types
|
|
])
|
|
weight_description.arrange(buff = LARGE_BUFF)
|
|
weight_description.next_to(boxes, UP, aligned_edge = LEFT)
|
|
|
|
self.play(FadeIn(boxes))
|
|
self.play(Write(weight_description))
|
|
self.wait()
|
|
|
|
self.set_color_box = boxes
|
|
self.weight_description = weight_description
|
|
|
|
def mention_equivalence_to_discrete_case(self):
|
|
morty = Mortimer()
|
|
morty.flip()
|
|
morty.to_edge(DOWN)
|
|
morty.shift(LEFT)
|
|
self.play(FadeIn(morty))
|
|
self.play(PiCreatureSays(
|
|
morty,
|
|
"""This is equivalent to
|
|
the discrete case. """,
|
|
bubble_kwargs = {
|
|
"height" : 3,
|
|
"direction" : LEFT,
|
|
}
|
|
))
|
|
self.play(Blink(morty))
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [
|
|
morty, morty.bubble, morty.bubble.content
|
|
])))
|
|
|
|
def shift_divide_off_tick_marks(self):
|
|
groups = self.groups
|
|
slice_indices = self.slice_indices
|
|
v_lines = self.v_lines
|
|
|
|
left_segment = groups[1][0][0]
|
|
left_tick = groups[1][1][0]
|
|
right_segment = groups[-1][0][0]
|
|
right_tick = groups[-1][1][0]
|
|
|
|
segment_width = left_segment.get_width()
|
|
|
|
for mob in left_segment, right_segment:
|
|
mob.parts = VGroup(
|
|
mob.copy().pointwise_become_partial(mob, 0, 0.5),
|
|
mob.copy().pointwise_become_partial(mob, 0.5, 1),
|
|
)
|
|
self.remove(mob)
|
|
self.add(mob.parts)
|
|
restorers = [left_segment.parts, left_tick, right_segment.parts, right_tick]
|
|
for mob in restorers:
|
|
mob.save_state()
|
|
|
|
emerald_segments = VGroup(*[
|
|
segment
|
|
for segment in list(groups[0][0])+list(groups[2][0])
|
|
if segment.get_color() == Color(self.jewel_colors[1])
|
|
if segment is not right_segment
|
|
])
|
|
emerald_segments.add(
|
|
left_segment.parts[0],
|
|
right_segment.parts[1],
|
|
)
|
|
emerald_segments.sort()
|
|
|
|
self.play(v_lines.shift, segment_width*RIGHT/2)
|
|
self.play(*[
|
|
ApplyMethod(mob.shift, vect)
|
|
for mob, vect in [
|
|
(left_segment.parts[0], UP),
|
|
(left_tick, UP),
|
|
(right_segment.parts[0], DOWN),
|
|
(right_tick, DOWN),
|
|
]
|
|
])
|
|
self.wait()
|
|
|
|
words = TextMobject("Cut part way through segment")
|
|
words.to_edge(RIGHT)
|
|
words.shift(2*UP)
|
|
arrow1 = Arrow(words.get_bottom(), left_segment.parts[0].get_right())
|
|
arrow2 = Arrow(words.get_bottom(), right_segment.parts[1].get_left())
|
|
VGroup(words, arrow1, arrow2).set_color(RED)
|
|
|
|
self.play(Write(words), ShowCreation(arrow1))
|
|
self.wait()
|
|
|
|
emerald_segments.save_state()
|
|
emerald_segments.generate_target()
|
|
emerald_segments.target.arrange()
|
|
emerald_segments.target.move_to(2*DOWN)
|
|
brace = Brace(emerald_segments.target, DOWN)
|
|
label = VGroup(
|
|
TexMobject("5\\left( 1/18 \\right)"),
|
|
Jewel(color = self.jewel_colors[1])
|
|
).arrange()
|
|
label.next_to(brace, DOWN)
|
|
self.play(MoveToTarget(emerald_segments))
|
|
self.play(GrowFromCenter(brace))
|
|
self.play(Write(label))
|
|
self.wait()
|
|
broken_pair = VGroup(*emerald_segments[2:4])
|
|
broken_pair.save_state()
|
|
self.play(broken_pair.shift, 0.5*UP)
|
|
vect = broken_pair[1].get_left()-broken_pair[1].get_right()
|
|
self.play(
|
|
broken_pair[0].shift, -vect/2,
|
|
broken_pair[1].shift, vect/2,
|
|
)
|
|
self.wait()
|
|
self.play(broken_pair.space_out_submobjects)
|
|
self.play(broken_pair.restore)
|
|
self.wait()
|
|
self.play(
|
|
emerald_segments.restore,
|
|
*list(map(FadeOut, [brace, label]))
|
|
)
|
|
|
|
self.wait()
|
|
self.play(ShowCreation(arrow2))
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [words, arrow1, arrow2])))
|
|
|
|
for line in v_lines:
|
|
self.play(line.shift, segment_width*LEFT/2)
|
|
self.play(*[mob.restore for mob in restorers])
|
|
self.remove(left_segment.parts, right_segment.parts)
|
|
self.add(left_segment, right_segment)
|
|
|
|
class ThinkAboutTheChoices(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("""
|
|
Think about the choices
|
|
behind a division...
|
|
""")
|
|
self.change_student_modes(
|
|
*["pondering"]*3,
|
|
look_at_arg = FRAME_X_RADIUS*RIGHT+FRAME_Y_RADIUS*DOWN
|
|
)
|
|
self.wait(3)
|
|
|
|
class ChoicesInNecklaceCutting(ReconfigurableScene):
|
|
CONFIG = {
|
|
"num_continuous_division_searches" : 4,
|
|
"denoms" : [6, 3, 2],
|
|
"necklace_center" : DOWN,
|
|
"thief_box_offset" : 1.2,
|
|
}
|
|
def construct(self):
|
|
self.add_necklace()
|
|
self.choose_places_to_cut()
|
|
self.show_three_numbers_adding_to_one()
|
|
self.make_binary_choice()
|
|
|
|
def add_necklace(self):
|
|
width, colors, num_per_color = [
|
|
MakeTwoJewelCaseContinuous.CONFIG[key]
|
|
for key in [
|
|
"necklace_width", "jewel_colors", "num_per_jewel"
|
|
]
|
|
]
|
|
color_list = list(it.chain(*[
|
|
num*[color]
|
|
for num, color in zip(num_per_color, colors)
|
|
]))
|
|
random.shuffle(color_list)
|
|
|
|
interval = UnitInterval(
|
|
tick_frequency = 1./sum(num_per_color),
|
|
tick_size = 0.2,
|
|
numbers_with_elongated_ticks = [],
|
|
)
|
|
interval.stretch_to_fit_width(width)
|
|
interval.shift(self.necklace_center)
|
|
tick_marks = interval.tick_marks
|
|
tick_marks.set_stroke(WHITE, width = 2)
|
|
|
|
segments = VGroup()
|
|
for l_tick, r_tick, color in zip(tick_marks, tick_marks[1:], color_list):
|
|
segment = Line(
|
|
l_tick.get_center(),
|
|
r_tick.get_center(),
|
|
color = color
|
|
)
|
|
segments.add(segment)
|
|
|
|
self.necklace = VGroup(segments, tick_marks)
|
|
self.add(self.necklace)
|
|
|
|
self.interval = interval
|
|
|
|
def choose_places_to_cut(self):
|
|
v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)])
|
|
final_num_pair = np.cumsum([1./d for d in self.denoms[:2]])
|
|
|
|
num_pairs = [
|
|
sorted([random.random(), random.random()])
|
|
for x in range(self.num_continuous_division_searches)
|
|
] + [final_num_pair]
|
|
|
|
point_pairs = [
|
|
list(map(self.interval.number_to_point, num_pair))
|
|
for num_pair in num_pairs
|
|
]
|
|
|
|
for line, point in zip(v_lines, point_pairs[0]):
|
|
line.move_to(point)
|
|
self.play(ShowCreation(v_lines))
|
|
for point_pair in point_pairs[1:]:
|
|
self.wait()
|
|
self.play(*[
|
|
ApplyMethod(line.move_to, point)
|
|
for line, point in zip(v_lines, point_pair)
|
|
])
|
|
self.wait()
|
|
|
|
self.division_points = list(it.chain(
|
|
[self.interval.get_left()],
|
|
point_pairs[-1],
|
|
[self.interval.get_right()]
|
|
))
|
|
|
|
self.v_lines = v_lines
|
|
|
|
def show_three_numbers_adding_to_one(self):
|
|
points = self.division_points
|
|
braces = [
|
|
Brace(Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2))
|
|
for p1, p2 in zip(points, points[1:])
|
|
]
|
|
for char, denom, brace in zip("abc", self.denoms, braces):
|
|
brace.label = brace.get_text("$%s$"%char)
|
|
brace.concrete_label = brace.get_text("$\\frac{1}{%d}$"%denom)
|
|
VGroup(
|
|
brace.label,
|
|
brace.concrete_label
|
|
).set_color(YELLOW)
|
|
|
|
words = TextMobject(
|
|
"1) Choose", "$a$, $b$, $c$", "so that", "$a+b+c = 1$"
|
|
)
|
|
words[1].set_color(YELLOW)
|
|
words[3].set_color(YELLOW)
|
|
words.to_corner(UP+LEFT)
|
|
|
|
self.play(*it.chain(*[
|
|
[GrowFromCenter(brace), Write(brace.label)]
|
|
for brace in braces
|
|
]))
|
|
self.play(Write(words))
|
|
self.wait(2)
|
|
self.play(*[
|
|
ReplacementTransform(brace.label, brace.concrete_label)
|
|
for brace in braces
|
|
])
|
|
self.wait()
|
|
self.wiggle_v_lines()
|
|
self.wait()
|
|
self.transition_to_alt_config(denoms = [3, 3, 3])
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, list(braces) + [
|
|
brace.concrete_label for brace in braces
|
|
])))
|
|
|
|
self.choice_one_words = words
|
|
|
|
def make_binary_choice(self):
|
|
groups = self.get_groups()
|
|
boxes, labels = self.get_boxes_and_labels()
|
|
arrow_pairs, curr_arrows = self.get_choice_arrow_pairs(groups)
|
|
words = TextMobject("2) Make a binary choice for each segment")
|
|
words.next_to(
|
|
self.choice_one_words, DOWN,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
|
|
self.play(Write(words))
|
|
self.play(*list(map(FadeIn, [boxes, labels])))
|
|
for binary_choices in it.product(*[[0, 1]]*3):
|
|
self.play(*[
|
|
ApplyMethod(group.move_to, group.target_points[choice])
|
|
for group, choice in zip(groups, binary_choices)
|
|
] + [
|
|
Transform(
|
|
curr_arrow, arrow_pair[choice],
|
|
path_arc = np.pi
|
|
)
|
|
for curr_arrow, arrow_pair, choice in zip(
|
|
curr_arrows, arrow_pairs, binary_choices
|
|
)
|
|
])
|
|
self.wait()
|
|
|
|
######
|
|
|
|
def get_groups(self, indices = None):
|
|
segments, tick_marks = self.necklace
|
|
if indices is None:
|
|
n_segments = len(segments)
|
|
indices = [0, n_segments/6, n_segments/2, n_segments]
|
|
|
|
groups = [
|
|
VGroup(
|
|
VGroup(*segments[i1:i2]),
|
|
VGroup(*tick_marks[i1:i2]),
|
|
)
|
|
for i1, i2 in zip(indices, indices[1:])
|
|
]
|
|
for group, index in zip(groups, indices[1:]):
|
|
group[1].add(tick_marks[index].copy())
|
|
groups[-1][1].add(tick_marks[-1])
|
|
|
|
for group in groups:
|
|
group.target_points = [
|
|
group.get_center() + self.thief_box_offset*vect
|
|
for vect in (UP, DOWN)
|
|
]
|
|
|
|
return groups
|
|
|
|
def get_boxes_and_labels(self):
|
|
box = Rectangle(
|
|
height = self.necklace.get_height()+SMALL_BUFF,
|
|
width = self.necklace.get_width()+2*SMALL_BUFF,
|
|
stroke_width = 0,
|
|
fill_color = WHITE,
|
|
fill_opacity = 0.25
|
|
)
|
|
box.move_to(self.necklace)
|
|
|
|
boxes = VGroup(*[
|
|
box.copy().shift(self.thief_box_offset*vect)
|
|
for vect in (UP, DOWN)
|
|
])
|
|
labels = VGroup(*[
|
|
TextMobject(
|
|
"Thief %d"%(i+1)
|
|
).next_to(box, UP, aligned_edge = RIGHT)
|
|
for i, box in enumerate(boxes)
|
|
])
|
|
return boxes, labels
|
|
|
|
def get_choice_arrow_pairs(self, groups):
|
|
arrow = TexMobject("\\uparrow")
|
|
arrow_pairs = [
|
|
[arrow.copy(), arrow.copy().rotate(np.pi)]
|
|
for group in groups
|
|
]
|
|
pre_arrow_points = [
|
|
VectorizedPoint(group.get_center())
|
|
for group in groups
|
|
]
|
|
for point, arrow_pair in zip(pre_arrow_points, arrow_pairs):
|
|
for arrow, color in zip(arrow_pair, [GREEN, RED]):
|
|
arrow.set_color(color)
|
|
arrow.move_to(point.get_center())
|
|
return arrow_pairs, pre_arrow_points
|
|
|
|
def wiggle_v_lines(self):
|
|
self.play(
|
|
*it.chain(*[
|
|
[
|
|
line.rotate_in_place, np.pi/12, vect,
|
|
line.set_color, RED
|
|
]
|
|
for line, vect in zip(self.v_lines, [OUT, IN])
|
|
]),
|
|
rate_func = wiggle
|
|
)
|
|
|
|
class CompareThisToSphereChoice(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("""
|
|
Compare this to choosing
|
|
a point on the sphere.
|
|
""")
|
|
self.change_student_modes(
|
|
*["pondering"]*3,
|
|
look_at_arg = FRAME_X_RADIUS*RIGHT+FRAME_Y_RADIUS*DOWN
|
|
)
|
|
self.wait(3)
|
|
|
|
class SimpleRotatingSphereWithPoint(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ChoicesForSpherePoint(GeneralizeBorsukUlam):
|
|
def construct(self):
|
|
self.add_sphere_set()
|
|
self.initialize_words()
|
|
self.play(Write(self.choice_one_words))
|
|
self.wait()
|
|
self.show_example_choices()
|
|
self.show_binary_choices()
|
|
|
|
def get_tuple(self):
|
|
tup = TexMobject("(x, y, z)")
|
|
for i, color in zip([1, 3, 5], self.colors):
|
|
tup[i].set_color(color)
|
|
return tup
|
|
|
|
def get_condition(self):
|
|
condition = TexMobject("x^2+y^2+z^2 = 1")
|
|
for i, color in zip([0, 3, 6], self.colors):
|
|
VGroup(*condition[i:i+2]).set_color(color)
|
|
return condition
|
|
|
|
def add_sphere_set(self):
|
|
sphere_set = self.get_sphere_set()
|
|
sphere_set.scale(0.7)
|
|
sphere_set.to_edge(RIGHT)
|
|
sphere_set.shift(UP)
|
|
|
|
self.add(sphere_set)
|
|
self.sphere_set = sphere_set
|
|
|
|
def initialize_words(self):
|
|
choice_one_words = TextMobject(
|
|
"1) Choose", "$x^2$, $y^2$, $z^2$",
|
|
"so that", "$x^2+y^2+z^2 = 1$"
|
|
)
|
|
for i in 1, 3:
|
|
for j, color in zip([0, 3, 6], self.colors):
|
|
VGroup(*choice_one_words[i][j:j+2]).set_color(color)
|
|
choice_one_words.to_corner(UP+LEFT)
|
|
|
|
choice_two_words = TextMobject(
|
|
"2) Make a binary choice for each one"
|
|
)
|
|
choice_two_words.next_to(
|
|
choice_one_words, DOWN,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
|
|
self.choice_one_words = choice_one_words
|
|
self.choice_two_words = choice_two_words
|
|
|
|
def show_example_choices(self):
|
|
choices = VGroup(*[
|
|
TexMobject(*tex).set_color(color)
|
|
for color, tex in zip(self.colors, [
|
|
("x", "^2 = ", "1/6"),
|
|
("y", "^2 = ", "1/3"),
|
|
("z", "^2 = ", "1/2"),
|
|
])
|
|
])
|
|
choices.arrange(
|
|
DOWN,
|
|
buff = LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
choices.set_height(FRAME_Y_RADIUS)
|
|
choices.to_edge(LEFT)
|
|
choices.shift(DOWN)
|
|
|
|
self.play(FadeIn(
|
|
choices,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
|
|
self.choices = choices
|
|
|
|
def show_binary_choices(self):
|
|
for choice in self.choices:
|
|
var_tex = choice.expression_parts[0]
|
|
frac_tex = choice.expression_parts[2]
|
|
sqrts = VGroup(*[
|
|
TexMobject(
|
|
var_tex + "=" + sign + \
|
|
"\\sqrt{%s}"%frac_tex)
|
|
for sign in ["+", "-"]
|
|
])
|
|
for sqrt in sqrts:
|
|
sqrt.scale(0.6)
|
|
sqrts.arrange(DOWN)
|
|
sqrts.next_to(choice, RIGHT, buff = LARGE_BUFF)
|
|
sqrts.set_color(choice.get_color())
|
|
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
choice.get_right(), sqrt.get_left(),
|
|
color = WHITE,
|
|
tip_length = 0.1,
|
|
buff = SMALL_BUFF
|
|
)
|
|
for sqrt in sqrts
|
|
])
|
|
|
|
self.play(ShowCreation(arrows))
|
|
self.play(FadeIn(sqrts, lag_ratio = 0.5))
|
|
self.play(Write(self.choice_two_words))
|
|
self.wait()
|
|
|
|
class NecklaceDivisionSphereAssociation(ChoicesInNecklaceCutting):
|
|
CONFIG = {
|
|
"xyz_colors" : color_gradient([GREEN_B, BLUE], 3),
|
|
"necklace_center" : DOWN,
|
|
"thief_box_offset" : 1.6,
|
|
"denoms" : [6, 3, 2],
|
|
}
|
|
def construct(self):
|
|
self.add_necklace()
|
|
self.add_sphere_point_label()
|
|
self.choose_places_to_cut()
|
|
self.add_braces()
|
|
self.add_boxes_and_labels()
|
|
self.show_binary_choice_association()
|
|
self.ask_about_antipodal_pairs()
|
|
|
|
def add_sphere_point_label(self):
|
|
label = TextMobject(
|
|
"$(x, y, z)$",
|
|
"such that",
|
|
"$x^2 + y^2 + z^2 = 1$"
|
|
)
|
|
for i, j_list in (0, [1, 3, 5]), (2, [0, 3, 6]):
|
|
for j, color in zip(j_list, self.xyz_colors):
|
|
label[i][j].set_color(color)
|
|
label.to_corner(UP+RIGHT)
|
|
|
|
ghost_sphere_point = VectorizedPoint()
|
|
ghost_sphere_point.to_corner(UP+LEFT, buff = LARGE_BUFF)
|
|
ghost_sphere_point.shift(2*RIGHT)
|
|
|
|
arrow = Arrow(
|
|
label.get_left(), ghost_sphere_point,
|
|
color = WHITE
|
|
)
|
|
|
|
self.add(label, arrow)
|
|
|
|
self.sphere_point_label = label
|
|
|
|
def add_braces(self):
|
|
points = self.division_points
|
|
braces = [
|
|
Brace(
|
|
Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2),
|
|
UP
|
|
)
|
|
for p1, p2 in zip(points, points[1:])
|
|
]
|
|
for char, brace, color, denom in zip("xyz", braces, self.xyz_colors, self.denoms):
|
|
brace.label = brace.get_text(
|
|
"$%s^2$"%char, "$= 1/%d$"%denom,
|
|
buff = SMALL_BUFF
|
|
)
|
|
brace.label.set_color(color)
|
|
if brace.label.get_right()[0] > brace.get_right()[0]:
|
|
brace.label.next_to(
|
|
brace, UP, buff = SMALL_BUFF,
|
|
aligned_edge = RIGHT
|
|
)
|
|
|
|
self.play(*it.chain(
|
|
list(map(GrowFromCenter, braces)),
|
|
[Write(brace.label) for brace in braces]
|
|
))
|
|
self.wait()
|
|
|
|
self.braces = braces
|
|
|
|
def add_boxes_and_labels(self):
|
|
boxes, labels = self.get_boxes_and_labels()
|
|
self.play(*list(map(FadeIn, [boxes, labels])))
|
|
self.wait()
|
|
|
|
def show_binary_choice_association(self):
|
|
groups = self.get_groups()
|
|
self.swapping_anims = []
|
|
final_choices = [1, 0, 1]
|
|
quads = list(zip(self.braces, self.denoms, groups, final_choices))
|
|
for brace, denom, group, final_choice in quads:
|
|
char = brace.label.args[0][1]
|
|
choices = [
|
|
TexMobject(
|
|
char, "=", sign, "\\sqrt{\\frac{1}{%d}}"%denom
|
|
)
|
|
for sign in ("+", "-")
|
|
]
|
|
for choice, color in zip(choices, [GREEN, RED]):
|
|
# choice[0].set_color(brace.label.get_color())
|
|
choice[2].set_color(color)
|
|
choice.scale(0.8)
|
|
choice.move_to(group)
|
|
if choice.get_width() > 0.8*group.get_width():
|
|
choice.next_to(group.get_right(), LEFT, buff = MED_SMALL_BUFF)
|
|
original_choices = [m.copy() for m in choices]
|
|
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(brace.label[0], brace, brace.label[1]),
|
|
choices[0]
|
|
),
|
|
group.move_to, group.target_points[0]
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Transform(*choices),
|
|
group.move_to, group.target_points[1]
|
|
)
|
|
self.wait()
|
|
if final_choice == 0:
|
|
self.play(
|
|
Transform(choices[0], original_choices[0]),
|
|
group.move_to, group.target_points[0]
|
|
)
|
|
self.swapping_anims += [
|
|
Transform(choices[0], original_choices[1-final_choice]),
|
|
group.move_to, group.target_points[1-final_choice]
|
|
]
|
|
|
|
def ask_about_antipodal_pairs(self):
|
|
question = TextMobject("What do antipodal points signify?")
|
|
question.move_to(self.sphere_point_label, LEFT)
|
|
question.set_color(MAROON_B)
|
|
antipodal_tex = TexMobject(
|
|
"(x, y, z) \\rightarrow (-x, -y, -z)"
|
|
)
|
|
antipodal_tex.next_to(question, DOWN, aligned_edge = LEFT)
|
|
|
|
self.play(FadeOut(self.sphere_point_label))
|
|
self.play(FadeIn(question))
|
|
self.wait()
|
|
self.play(Write(antipodal_tex))
|
|
self.wait()
|
|
self.wiggle_v_lines()
|
|
self.wait()
|
|
self.play(*self.swapping_anims)
|
|
self.wait()
|
|
|
|
class SimpleRotatingSphereWithAntipodes(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class TotalLengthOfEachJewelEquals(NecklaceDivisionSphereAssociation, ThreeDScene):
|
|
CONFIG = {
|
|
"random_seed" : 1,
|
|
"thief_box_offset" : 1.2,
|
|
}
|
|
def construct(self):
|
|
random.seed(self.random_seed)
|
|
self.add_necklace()
|
|
self.add_boxes_and_labels()
|
|
self.find_fair_division()
|
|
self.demonstrate_fair_division()
|
|
self.perform_antipodal_swap()
|
|
|
|
def find_fair_division(self):
|
|
segments, tick_marks = self.necklace
|
|
segments.sort()
|
|
segment_colors = [
|
|
segment.get_color()
|
|
for segment in segments
|
|
]
|
|
indices = self.get_fair_division_indices(segment_colors)
|
|
groups = self.get_groups(
|
|
[0] + list(np.array(indices)+1) + [len(segments)]
|
|
)
|
|
self.add(*groups)
|
|
binary_choice = [0, 1, 0]
|
|
|
|
v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)])
|
|
v_lines.move_to(self.necklace)
|
|
self.play(ShowCreation(v_lines))
|
|
self.play(*[
|
|
ApplyMethod(line.move_to, segments[index].get_right())
|
|
for line, index in zip(v_lines, indices)
|
|
])
|
|
self.wait()
|
|
self.play(*[
|
|
ApplyMethod(group.move_to, group.target_points[choice])
|
|
for group, choice in zip(groups, binary_choice)
|
|
])
|
|
self.wait()
|
|
|
|
self.groups = groups
|
|
self.v_lines = v_lines
|
|
|
|
def get_fair_division_indices(self, colors):
|
|
colors = np.array(list(colors))
|
|
color_types = list(map(Color, set([c.get_hex_l() for c in colors])))
|
|
type_to_count = dict([
|
|
(color, sum(colors == color))
|
|
for color in color_types
|
|
])
|
|
for i1, i2 in it.combinations(list(range(1, len(colors)-1)), 2):
|
|
bools = [
|
|
sum(colors[i1:i2] == color) == type_to_count[color]/2
|
|
for color in color_types
|
|
]
|
|
if np.all(bools):
|
|
return i1, i2
|
|
raise Exception("No fair division found")
|
|
|
|
def demonstrate_fair_division(self):
|
|
segments, tick_marks = self.necklace
|
|
color_types = list(map(Color, set([
|
|
segment.get_color().get_hex_l()
|
|
for segment in segments
|
|
])))
|
|
top_segments = VGroup(*it.chain(
|
|
self.groups[0][0],
|
|
self.groups[2][0],
|
|
))
|
|
bottom_segments = self.groups[1][0]
|
|
for color in color_types:
|
|
monochrome_groups = [
|
|
VGroup(*[segment for segment in segment_group if segment.get_color() == color])
|
|
for segment_group in (top_segments, bottom_segments)
|
|
]
|
|
labels = VGroup()
|
|
for i, group in enumerate(monochrome_groups):
|
|
group.save_state()
|
|
group.generate_target()
|
|
group.target.arrange(buff = SMALL_BUFF)
|
|
brace = Brace(group.target, UP)
|
|
label = VGroup(
|
|
TextMobject("Thief %d"%(i+1)),
|
|
Jewel(color = group[0].get_color())
|
|
)
|
|
label.arrange()
|
|
label.next_to(brace, UP)
|
|
full_group = VGroup(group.target, brace, label)
|
|
vect = LEFT if i == 0 else RIGHT
|
|
full_group.next_to(ORIGIN, vect, buff = MED_LARGE_BUFF)
|
|
full_group.to_edge(UP)
|
|
labels.add(VGroup(brace, label))
|
|
equals = TexMobject("=")
|
|
equals.next_to(monochrome_groups[0].target, RIGHT)
|
|
labels[-1].add(equals)
|
|
|
|
for group, label in zip(monochrome_groups, labels):
|
|
self.play(
|
|
MoveToTarget(group),
|
|
FadeIn(label),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(labels),
|
|
*[group.restore for group in monochrome_groups]
|
|
)
|
|
self.wait()
|
|
|
|
def perform_antipodal_swap(self):
|
|
binary_choices_list = [(1, 0, 1), (0, 1, 0)]
|
|
for binary_choices in binary_choices_list:
|
|
self.play(*[
|
|
ApplyMethod(
|
|
group.move_to,
|
|
group.target_points[choice]
|
|
)
|
|
for group, choice in zip(self.groups, binary_choices)
|
|
])
|
|
self.wait()
|
|
|
|
class ExclaimBorsukUlam(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Borsuk-Ulam!",
|
|
target_mode = "hooray"
|
|
)
|
|
self.play(*[
|
|
ApplyMethod(pi.change_mode, "hooray")
|
|
for pi in self.get_pi_creatures()
|
|
])
|
|
self.wait(3)
|
|
|
|
class ShowFunctionDiagram(TotalLengthOfEachJewelEquals, ReconfigurableScene):
|
|
CONFIG = {
|
|
"necklace_center" : ORIGIN,
|
|
"camera_class" : ThreeDCamera,
|
|
"thief_box_offset" : 0.3,
|
|
"make_up_fair_division_indices" : False,
|
|
}
|
|
def construct(self):
|
|
self.add_necklace()
|
|
self.add_number_pair()
|
|
self.swap_necklace_allocation()
|
|
self.add_sphere_arrow()
|
|
|
|
def add_necklace(self):
|
|
random.seed(self.random_seed)
|
|
ChoicesInNecklaceCutting.add_necklace(self)
|
|
self.necklace.set_width(FRAME_X_RADIUS-1)
|
|
self.necklace.to_edge(UP, buff = LARGE_BUFF)
|
|
self.necklace.to_edge(LEFT, buff = SMALL_BUFF)
|
|
self.add(self.necklace)
|
|
|
|
self.find_fair_division()
|
|
|
|
def add_number_pair(self):
|
|
plane_classes = [
|
|
JewelPairPlane(
|
|
skip_animations = True,
|
|
thief_number = x
|
|
)
|
|
for x in (1, 2)
|
|
]
|
|
t1_plane, t2_plane = planes = VGroup(*[
|
|
VGroup(*plane_class.get_top_level_mobjects())
|
|
for plane_class in plane_classes
|
|
])
|
|
planes.set_width(FRAME_X_RADIUS)
|
|
planes.to_edge(RIGHT)
|
|
self.example_coords = plane_classes[0].example_coords[0]
|
|
|
|
arrow = Arrow(
|
|
self.necklace.get_corner(DOWN+RIGHT),
|
|
self.example_coords,
|
|
color = YELLOW
|
|
)
|
|
|
|
self.play(ShowCreation(arrow))
|
|
self.play(Write(t1_plane), Animation(arrow))
|
|
self.wait()
|
|
clean_state = VGroup(*self.mobjects).family_members_with_points()
|
|
self.clear()
|
|
self.add(*clean_state)
|
|
self.transition_to_alt_config(
|
|
make_up_fair_division_indices = True
|
|
)
|
|
self.wait()
|
|
t1_plane.save_state()
|
|
self.play(
|
|
Transform(*planes, path_arc = np.pi),
|
|
Animation(arrow)
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
ApplyMethod(t1_plane.restore, path_arc = np.pi),
|
|
Animation(arrow)
|
|
)
|
|
self.wait()
|
|
|
|
def swap_necklace_allocation(self):
|
|
for choices in [(1, 0, 1), (0, 1, 0)]:
|
|
self.play(*[
|
|
ApplyMethod(group.move_to, group.target_points[i])
|
|
for group, i in zip(self.groups, choices)
|
|
])
|
|
self.wait()
|
|
|
|
def add_sphere_arrow(self):
|
|
up_down_arrow = TexMobject("\\updownarrow")
|
|
up_down_arrow.scale(1.5)
|
|
up_down_arrow.set_color(YELLOW)
|
|
up_down_arrow.next_to(self.necklace, DOWN, buff = LARGE_BUFF)
|
|
|
|
to_plane_arrow = Arrow(
|
|
up_down_arrow.get_bottom() + DOWN+RIGHT,
|
|
self.example_coords,
|
|
color = YELLOW
|
|
)
|
|
|
|
self.play(Write(up_down_arrow))
|
|
self.wait()
|
|
self.play(ShowCreation(to_plane_arrow))
|
|
self.wait()
|
|
|
|
def get_fair_division_indices(self, *args):
|
|
if self.make_up_fair_division_indices:
|
|
return [9, 14]
|
|
else:
|
|
return TotalLengthOfEachJewelEquals.get_fair_division_indices(self, *args)
|
|
|
|
class JewelPairPlane(GraphScene):
|
|
CONFIG = {
|
|
"camera_class" : ThreeDCamera,
|
|
"x_labeled_nums" : [],
|
|
"y_labeled_nums" : [],
|
|
"thief_number" : 1,
|
|
"colors" : [BLUE, GREEN],
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
point = self.coords_to_point(4, 5)
|
|
dot = Dot(point, color = WHITE)
|
|
coord_pair = TexMobject(
|
|
"\\big(",
|
|
"\\text{Thief %d }"%self.thief_number, "X", ",",
|
|
"\\text{Thief %d }"%self.thief_number, "X",
|
|
"\\big)"
|
|
)
|
|
# coord_pair.scale(1.5)
|
|
to_replace = [coord_pair[i] for i in [2, 5]]
|
|
for mob, color in zip(to_replace, self.colors):
|
|
jewel = Jewel(color = color)
|
|
jewel.replace(mob)
|
|
coord_pair.remove(mob)
|
|
coord_pair.add(jewel)
|
|
coord_pair.next_to(dot, UP+RIGHT, buff = 0)
|
|
|
|
self.example_coords = VGroup(dot, coord_pair)
|
|
self.add(self.example_coords)
|
|
|
|
class WhatThisMappingActuallyLooksLikeWords(Scene):
|
|
def construct(self):
|
|
words = TextMobject("What this mapping actually looks like")
|
|
words.set_width(FRAME_WIDTH-1)
|
|
words.to_edge(DOWN)
|
|
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
class WhatAboutGeneralCase(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says("""
|
|
What about when
|
|
there's more than 2 jewels?
|
|
""")
|
|
self.change_student_modes("confused", None, "sassy")
|
|
self.wait()
|
|
self.play(self.get_teacher().change_mode, "thinking")
|
|
self.wait()
|
|
self.teacher_says(
|
|
"""Use Borsuk-Ulam for
|
|
higher-dimensional spheres """,
|
|
target_mode = "hooray"
|
|
)
|
|
self.change_student_modes(*["confused"]*3)
|
|
self.wait(2)
|
|
|
|
class Simple3DSpace(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class FourDBorsukUlam(GeneralizeBorsukUlam, PiCreatureScene):
|
|
CONFIG = {
|
|
"n_dims" : 4,
|
|
"use_morty" : False,
|
|
}
|
|
def setup(self):
|
|
GeneralizeBorsukUlam.setup(self)
|
|
PiCreatureScene.setup(self)
|
|
self.pi_creature.to_corner(DOWN+LEFT, buff = MED_SMALL_BUFF)
|
|
|
|
def construct(self):
|
|
sphere_set = self.get_sphere_set()
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
f = TexMobject("f")
|
|
output_space = self.get_output_space()
|
|
equation = self.get_equation()
|
|
|
|
sphere_set.to_corner(UP+LEFT)
|
|
arrow.next_to(sphere_set, RIGHT)
|
|
f.next_to(arrow, UP)
|
|
output_space.next_to(arrow, RIGHT)
|
|
equation.next_to(sphere_set, DOWN, buff = LARGE_BUFF)
|
|
equation.to_edge(RIGHT)
|
|
lhs = VGroup(*equation[:2])
|
|
eq = equation[2]
|
|
rhs = VGroup(*equation[3:])
|
|
|
|
brace = Brace(Line(ORIGIN, 5*RIGHT))
|
|
brace.to_edge(RIGHT)
|
|
brace_text = brace.get_text("Triplets of numbers")
|
|
brace_text.shift_onto_screen()
|
|
|
|
self.play(FadeIn(sphere_set))
|
|
self.change_mode("confused")
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
Write(f)
|
|
)
|
|
self.play(Write(output_space))
|
|
self.wait()
|
|
self.change_mode("maybe")
|
|
self.wait(2)
|
|
self.change_mode("pondering")
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(brace_text)
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [brace, brace_text])))
|
|
self.wait()
|
|
self.play(
|
|
FadeIn(lhs),
|
|
self.pi_creature.change_mode, "raise_right_hand"
|
|
)
|
|
self.play(
|
|
ReplacementTransform(lhs.copy(), rhs),
|
|
Write(eq)
|
|
)
|
|
self.wait(2)
|
|
|
|
def get_sphere_set(self):
|
|
sphere_set = GeneralizeBorsukUlam.get_sphere_set(self)
|
|
brace = Brace(sphere_set)
|
|
text = brace.get_text("Hypersphere in 4D")
|
|
sphere_set.add(brace, text)
|
|
return sphere_set
|
|
|
|
class CircleToSphereToQMarks(Scene):
|
|
def construct(self):
|
|
pi_groups = VGroup()
|
|
modes = ["happy", "pondering", "pleading"]
|
|
shapes = [
|
|
Circle(color = BLUE, radius = 0.5),
|
|
VectorizedPoint(),
|
|
TexMobject("???")
|
|
]
|
|
for d, mode, shape in zip(it.count(2), modes, shapes):
|
|
randy = Randolph(mode = mode)
|
|
randy.scale(0.7)
|
|
bubble = randy.get_bubble(
|
|
height = 3, width = 4,
|
|
direction = LEFT
|
|
)
|
|
bubble.pin_to(randy)
|
|
bubble.position_mobject_inside(shape)
|
|
title = TextMobject("%dD"%d)
|
|
title.next_to(randy, UP)
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
arrow.next_to(randy.get_corner(UP+RIGHT))
|
|
pi_groups.add(VGroup(
|
|
randy, bubble, shape, title, arrow
|
|
))
|
|
|
|
pi_groups[-1].remove(pi_groups[-1][-1])
|
|
pi_groups.arrange(buff = -1)
|
|
for mob in pi_groups:
|
|
self.play(FadeIn(mob))
|
|
self.wait(2)
|
|
self.play(pi_groups[-1][0].change_mode, "thinking")
|
|
self.wait(2)
|
|
|
|
class BorsukPatreonThanks(PatreonThanks):
|
|
CONFIG = {
|
|
"specific_patrons" : [
|
|
"Ali Yahya",
|
|
"Meshal Alshammari",
|
|
"CrypticSwarm ",
|
|
"Ankit Agarwal",
|
|
"Yu Jun",
|
|
"Shelby Doolittle",
|
|
"Dave Nicponski",
|
|
"Damion Kistler",
|
|
"Juan Benet",
|
|
"Othman Alikhan",
|
|
"Justin Helps",
|
|
"Markus Persson",
|
|
"Dan Buchoff",
|
|
"Derek Dai",
|
|
"Joseph John Cox",
|
|
"Luc Ritchie",
|
|
"Guido Gambardella",
|
|
"Jerry Ling",
|
|
"Mark Govea",
|
|
"Vecht",
|
|
"Jonathan Eppele",
|
|
"Shimin Kuang",
|
|
"Rish Kundalia",
|
|
"Achille Brighton",
|
|
"Kirk Werklund",
|
|
"Ripta Pasay",
|
|
"Felipe Diniz",
|
|
]
|
|
}
|
|
|
|
class MortyLookingAtRectangle(Scene):
|
|
def construct(self):
|
|
morty = Mortimer()
|
|
morty.to_corner(DOWN+RIGHT)
|
|
url = TextMobject("www.thegreatcoursesplus.com/3blue1brown")
|
|
url.scale(0.75)
|
|
url.to_corner(UP+LEFT)
|
|
rect = Rectangle(height = 9, width = 16)
|
|
rect.set_height(5)
|
|
rect.next_to(url, DOWN)
|
|
rect.shift_onto_screen()
|
|
url.save_state()
|
|
url.next_to(morty.get_corner(UP+LEFT), UP)
|
|
url.shift_onto_screen()
|
|
|
|
self.add(morty)
|
|
self.play(
|
|
morty.change_mode, "raise_right_hand",
|
|
morty.look_at, url,
|
|
)
|
|
self.play(Write(url))
|
|
self.play(Blink(morty))
|
|
self.wait()
|
|
self.play(
|
|
url.restore,
|
|
morty.change_mode, "happy"
|
|
)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
self.play(Blink(morty))
|
|
for mode in ["pondering", "hooray", "happy", "pondering", "happy"]:
|
|
self.play(morty.change_mode, mode)
|
|
self.wait(2)
|
|
self.play(Blink(morty))
|
|
self.wait(2)
|
|
|
|
class RotatingThreeDSphereProjection(Scene):
|
|
CONFIG = {
|
|
"camera_class" : ThreeDCamera,
|
|
}
|
|
def construct(self):
|
|
sphere = VGroup(*[
|
|
Circle(radius = np.sin(t)).shift(np.cos(t)*OUT)
|
|
for t in np.linspace(0, np.pi, 20)
|
|
])
|
|
sphere.set_stroke(BLUE, width = 2)
|
|
# sphere.set_fill(BLUE, opacity = 0.1)
|
|
|
|
self.play(Rotating(
|
|
sphere, axis = RIGHT+OUT,
|
|
run_time = 10
|
|
))
|
|
self.repeat_frames(4)
|
|
|
|
class FourDSphereProjectTo4D(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class Test(Scene):
|
|
CONFIG = {
|
|
"camera_class" : ThreeDCamera,
|
|
}
|
|
def construct(self):
|
|
randy = Randolph()
|
|
necklace = Necklace()
|
|
necklace.insert_n_curves(20)
|
|
# necklace.apply_function(
|
|
# lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP
|
|
# )
|
|
necklace.set_width(randy.get_width() + 1)
|
|
necklace.move_to(randy)
|
|
|
|
self.add(randy, necklace)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|