3b1b-manim/old_projects/borsuk.py
2019-05-02 20:36:14 -07:00

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)