mirror of
https://github.com/3b1b/videos.git
synced 2025-08-05 16:48:47 +00:00
1095 lines
33 KiB
Python
1095 lines
33 KiB
Python
from manim_imports_ext import *
|
|
|
|
|
|
class RandomChordScene(Scene):
|
|
title = ""
|
|
radius = 3.5
|
|
n_samples = 1000
|
|
long_color = BLUE
|
|
short_color = WHITE
|
|
chord_width = 0.5
|
|
chord_opacity = 0.35
|
|
run_time = 20
|
|
include_triangle = True
|
|
|
|
def construct(self):
|
|
circle = self.circle = Circle(radius=self.radius)
|
|
circle.set_stroke(GREY_B, 3)
|
|
circle.to_edge(LEFT)
|
|
|
|
chords = self.get_chords(circle)
|
|
flash_chords = chords.copy()
|
|
flash_chords.set_stroke(width=3, opacity=1)
|
|
indicators = Group(*map(self.get_method_indicator, chords))
|
|
|
|
title = Text(self.title)
|
|
title.set_x(FRAME_WIDTH / 4).to_edge(UP)
|
|
|
|
triangle = self.get_triangle(circle)
|
|
|
|
if not self.include_triangle:
|
|
triangle.set_opacity(0)
|
|
|
|
self.add(circle, title, triangle)
|
|
|
|
for s, rt in (slice(0, 16), 8), (slice(18, None), self.run_time):
|
|
fraction = self.get_fraction([c.long for c in chords[s]])
|
|
fraction.match_x(title)
|
|
fraction.match_y(circle)
|
|
self.add(fraction)
|
|
self.remove(triangle)
|
|
self.play(
|
|
ShowIncreasingSubsets(chords[s]),
|
|
ShowSubmobjectsOneByOne(flash_chords[s]),
|
|
ShowSubmobjectsOneByOne(indicators[s]),
|
|
Animation(triangle),
|
|
fraction.alpha_update,
|
|
rate_func=linear,
|
|
run_time=rt,
|
|
)
|
|
self.remove(flash_chords)
|
|
self.remove(indicators)
|
|
self.remove(fraction)
|
|
self.add(fraction)
|
|
self.wait()
|
|
|
|
def get_fraction(self, data):
|
|
tex = OldTex(
|
|
"{100", "\\over ", "100", "+", "100}", "= ", "0.500"
|
|
)
|
|
nl1 = Integer(100, edge_to_fix=ORIGIN) # Number of long chords
|
|
nl2 = Integer(100, edge_to_fix=RIGHT)
|
|
ns = Integer(100, edge_to_fix=LEFT) # Number of short chords
|
|
ratio = DecimalNumber(0, num_decimal_places=4)
|
|
fraction = VGroup(
|
|
nl1.move_to(tex[0]),
|
|
tex[1],
|
|
nl2.move_to(tex[2]),
|
|
tex[3],
|
|
ns.move_to(tex[4]),
|
|
tex[5],
|
|
ratio.move_to(tex[6], LEFT),
|
|
)
|
|
|
|
def update_fraction(frac, alpha):
|
|
subdata = data[:int(np.floor(alpha * len(data)))]
|
|
n_long = sum(subdata)
|
|
n_short = len(subdata) - n_long
|
|
frac[0].set_value(n_long)
|
|
frac[0].set_color(self.long_color)
|
|
frac[2].set_value(n_long)
|
|
frac[2].set_color(self.long_color)
|
|
frac[4].set_value(n_short)
|
|
frac[4].set_color(self.short_color)
|
|
|
|
if len(subdata) == 0:
|
|
frac[-2:].set_opacity(0)
|
|
else:
|
|
frac[-2:].set_opacity(1)
|
|
frac[6].set_value(n_long / len(subdata))
|
|
return frac
|
|
|
|
fraction.alpha_update = UpdateFromAlphaFunc(
|
|
fraction, update_fraction
|
|
)
|
|
return fraction
|
|
|
|
def get_chords(self, circle, chord_generator=None):
|
|
if chord_generator is None:
|
|
chord_generator = self.get_random_chord
|
|
tri_len = np.sqrt(3) * circle.get_width() / 2
|
|
chords = VGroup(*(
|
|
chord_generator(circle)
|
|
for x in range(self.n_samples)
|
|
))
|
|
for chord in chords:
|
|
chord.long = (chord.get_length() > tri_len)
|
|
chord.set_color(
|
|
self.long_color if chord.long else self.short_color
|
|
)
|
|
chords.set_stroke(
|
|
width=self.chord_width,
|
|
opacity=self.chord_opacity
|
|
)
|
|
return chords
|
|
|
|
def get_triangle(self, circle):
|
|
verts = [circle.pfp(a) for a in np.arange(0, 1, 1 / 3)]
|
|
triangle = Polygon(*verts)
|
|
triangle.rotate(-PI / 6, about_point=circle.get_center())
|
|
triangle.set_stroke(RED, 2, 1)
|
|
return triangle
|
|
|
|
def get_random_chord(self, circle):
|
|
return NotImplemented
|
|
|
|
def get_method_indicator(self, chord):
|
|
return NotImplemented
|
|
|
|
|
|
class PairOfPoints(RandomChordScene):
|
|
title = "Random pair of circle points"
|
|
|
|
@staticmethod
|
|
def get_random_chord(circle):
|
|
return Line(
|
|
circle.pfp(random.random()),
|
|
circle.pfp(random.random()),
|
|
)
|
|
|
|
@staticmethod
|
|
def get_method_indicator(chord):
|
|
dots = DotCloud([
|
|
chord.get_start(),
|
|
chord.get_end(),
|
|
])
|
|
dots.set_glow_factor(2)
|
|
dots.set_radius(0.25)
|
|
dots.set_color(YELLOW)
|
|
return dots
|
|
|
|
|
|
class CenterPoint(RandomChordScene):
|
|
title = "Random point in circle"
|
|
|
|
@staticmethod
|
|
def get_random_chord(circle):
|
|
x = y = 1
|
|
while x * x + y * y > 1:
|
|
x = random.uniform(-1, 1)
|
|
y = random.uniform(-1, 1)
|
|
return CenterPoint.chord_from_xy(x, y, circle)
|
|
|
|
@staticmethod
|
|
def chord_from_xy(x, y, circle):
|
|
n2 = x * x + y * y
|
|
temp_x = math.sqrt(n2)
|
|
temp_y = math.sqrt(1 - n2)
|
|
line = Line(
|
|
[temp_x, -temp_y, 0],
|
|
[temp_x, temp_y, 0],
|
|
)
|
|
line.rotate(angle_of_vector([x, y, 0]), about_point=ORIGIN)
|
|
line.scale(circle.get_width() / 2, about_point=ORIGIN)
|
|
line.shift(circle.get_center())
|
|
return line
|
|
|
|
@staticmethod
|
|
def get_method_indicator(chord):
|
|
dots = DotCloud([chord.get_center()])
|
|
dots.set_glow_factor(2)
|
|
dots.set_radius(0.25)
|
|
dots.set_color(YELLOW)
|
|
return dots
|
|
|
|
|
|
class RadialPoint(CenterPoint):
|
|
title = "Random point along radial line"
|
|
|
|
@staticmethod
|
|
def get_random_chord(circle):
|
|
angle = random.uniform(0, TAU)
|
|
dist = random.uniform(0, 1)
|
|
return CenterPoint.chord_from_xy(
|
|
dist * math.cos(angle),
|
|
dist * math.sin(angle),
|
|
circle
|
|
)
|
|
|
|
@staticmethod
|
|
def get_method_indicator(chord):
|
|
dot = super().get_method_indicator(chord)
|
|
line = Line(self.circle.get_center(), dot.get_center())
|
|
line.set_length(self.radius, about_point=line.get_start())
|
|
line.set_stroke(YELLOW, 1)
|
|
return Group(dot, line)
|
|
|
|
|
|
class CompareFirstTwoMethods(RandomChordScene):
|
|
chord_width = 0.1
|
|
chord_opacity = 0.8
|
|
|
|
def construct(self):
|
|
circles = Circle().get_grid(1, 2, buff=1)
|
|
circles.set_height(5)
|
|
circles.to_edge(DOWN, buff=LARGE_BUFF)
|
|
circles.set_stroke(GREY_B, 3)
|
|
|
|
titles = VGroup(
|
|
Text("Choose a pair of points"),
|
|
Text("Chose a center point"),
|
|
)
|
|
for title, circle in zip(titles, circles):
|
|
title.next_to(circle, UP, MED_LARGE_BUFF)
|
|
|
|
chord_groups = VGroup(
|
|
self.get_chords(circles[0], PairOfPoints.get_random_chord),
|
|
self.get_chords(circles[1], CenterPoint.get_random_chord),
|
|
)
|
|
flash_chord_groups = chord_groups.copy()
|
|
flash_chord_groups.set_stroke(width=3, opacity=1)
|
|
indicator_groups = Group(
|
|
Group(*(PairOfPoints.get_method_indicator(c) for c in chord_groups[0])),
|
|
Group(*(CenterPoint.get_method_indicator(c) for c in chord_groups[1])),
|
|
)
|
|
|
|
self.add(circles, titles)
|
|
|
|
self.play(
|
|
*map(ShowIncreasingSubsets, chord_groups),
|
|
*map(ShowSubmobjectsOneByOne, flash_chord_groups),
|
|
*map(ShowSubmobjectsOneByOne, indicator_groups),
|
|
rate_func=linear,
|
|
run_time=15,
|
|
)
|
|
self.play(
|
|
*map(FadeOut, (
|
|
flash_chord_groups[0][-1],
|
|
flash_chord_groups[1][-1],
|
|
indicator_groups[0][-1],
|
|
indicator_groups[1][-1],
|
|
)),
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
class SparseWords(Scene):
|
|
def construct(self):
|
|
words = Text("Sparser in the middle")
|
|
words.to_edge(DOWN, buff=MED_LARGE_BUFF)
|
|
arrow = Arrow(
|
|
words.get_top(),
|
|
[3.5, -0.5, 0],
|
|
)
|
|
arrow.set_color(YELLOW)
|
|
words.set_color(YELLOW)
|
|
|
|
self.play(
|
|
Write(words, run_time=1),
|
|
ShowCreation(arrow),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class PortionOfRadialLineInTriangle(Scene):
|
|
def construct(self):
|
|
# Circle and triangle
|
|
circle = Circle(radius=3.5)
|
|
circle.set_stroke(GREY_B, 4)
|
|
circle.rotate(-PI / 6)
|
|
triangle = Polygon(*(
|
|
circle.pfp(a) for a in np.arange(0, 1, 1 / 3)
|
|
))
|
|
triangle.set_stroke(GREEN, 2)
|
|
center_dot = Dot(circle.get_center().copy(), radius=0.04)
|
|
center_dot.set_fill(GREY_C, 1)
|
|
|
|
self.add(circle, center_dot, triangle)
|
|
|
|
# Radial line
|
|
radial_line = Line(circle.get_center().copy(), circle.pfp(1 / 6))
|
|
radial_line.set_stroke(WHITE, 2)
|
|
elbow = Elbow()
|
|
elbow.rotate(PI + radial_line.get_angle(), about_point=ORIGIN)
|
|
elbow.shift(radial_line.pfp(0.5) - ORIGIN)
|
|
elbow.match_style(radial_line)
|
|
half_line = radial_line.copy()
|
|
half_line.pointwise_become_partial(radial_line, 0, 0.5)
|
|
half_line.set_stroke(RED, 2)
|
|
half_label = OldTex("\\frac{1}{2}", font_size=30)
|
|
half_label.next_to(half_line.get_center(), DR, SMALL_BUFF)
|
|
half_label.set_color(RED)
|
|
|
|
self.play(ShowCreation(radial_line))
|
|
self.play(ShowCreation(elbow))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(half_line),
|
|
FadeIn(half_label, 0.2 * DOWN),
|
|
)
|
|
|
|
# Show sample chords along line
|
|
dot = TrueDot()
|
|
dot.set_glow_factor(5)
|
|
dot.set_radius(0.5)
|
|
dot.set_color(YELLOW)
|
|
dot.move_to(radial_line.pfp(0.1))
|
|
circle.rotate(PI / 6)
|
|
|
|
def get_chord():
|
|
p = dot.get_center().copy()
|
|
p /= (circle.get_width() / 2)
|
|
chord = CenterPoint.chord_from_xy(p[0], p[1], circle)
|
|
chord.set_stroke(BLUE, 4)
|
|
return chord
|
|
|
|
chord = always_redraw(get_chord)
|
|
|
|
self.play(
|
|
FadeIn(dot),
|
|
GrowFromCenter(chord)
|
|
)
|
|
self.wait()
|
|
for alpha in (0.5, 0.01, 0.4):
|
|
self.play(
|
|
dot.animate.move_to(radial_line.pfp(alpha)),
|
|
run_time=4,
|
|
)
|
|
|
|
self.add(dot)
|
|
|
|
# Embed
|
|
self.embed()
|
|
|
|
|
|
class RandomPointsFromVariousSpaces(Scene):
|
|
def construct(self):
|
|
# Interval
|
|
interval = UnitInterval()
|
|
interval.add_numbers()
|
|
top_words = OldTexText("Choose a random$^{*}$ ", "number between 0 and 1")
|
|
top_words.to_edge(UP)
|
|
subwords = OldTexText(
|
|
"($^{*}$Implicitly: According to a \\emph{uniform} distribution)",
|
|
color=GREY_A,
|
|
tex_to_color_map={"\\emph{uniform}": YELLOW},
|
|
font_size=36
|
|
)
|
|
subwords.next_to(top_words, DOWN, buff=MED_LARGE_BUFF)
|
|
|
|
dots = DotCloud([
|
|
interval.n2p(random.random())
|
|
for x in range(100)
|
|
])
|
|
dots.set_radius(0.5)
|
|
dots.set_glow_factor(10)
|
|
dots.set_color(YELLOW)
|
|
dots.add_updater(lambda m: m)
|
|
turn_animation_into_updater(
|
|
ShowCreation(dots, rate_func=linear, run_time=10)
|
|
)
|
|
|
|
self.add(interval, dots)
|
|
self.play(Write(top_words, run_time=1))
|
|
self.wait(2)
|
|
self.play(FadeIn(subwords, 0.5 * DOWN))
|
|
self.wait(6)
|
|
|
|
# Circle
|
|
circle = Circle()
|
|
circle.set_stroke(BLUE, 2)
|
|
circle.set_height(5)
|
|
circle.next_to(subwords, DOWN, buff=MED_LARGE_BUFF)
|
|
dots.clear_updaters()
|
|
dots.generate_target(use_deepcopy=True)
|
|
dots.target.set_points([
|
|
circle.pfp(interval.p2n(p))
|
|
for p in dots.get_points()
|
|
])
|
|
dots.target.set_radius(0)
|
|
|
|
circle_words = Text("point on a circle")
|
|
circle_words.move_to(top_words[1], UL)
|
|
circle_words.set_color(BLUE)
|
|
top_words[0].generate_target()
|
|
VGroup(top_words[0].target, circle_words).set_x(0)
|
|
|
|
self.play(
|
|
MoveToTarget(dots, run_time=1),
|
|
TransformFromCopy(
|
|
Line(interval.n2p(0), interval.n2p(1)).set_stroke(GREY_B, 1),
|
|
circle,
|
|
run_time=1,
|
|
),
|
|
FadeOut(interval),
|
|
FadeOut(top_words[1], UP),
|
|
FadeIn(circle_words, UP),
|
|
MoveToTarget(top_words[0]),
|
|
)
|
|
|
|
dots.set_points([
|
|
circle.pfp(random.random())
|
|
for x in range(25)
|
|
])
|
|
dots.set_radius(0.5)
|
|
dots.set_glow_factor(5)
|
|
dots.add_updater(lambda m: m)
|
|
self.add(dots)
|
|
self.play(ShowCreation(dots, rate_func=linear, run_time=2))
|
|
|
|
# Sphere
|
|
sphere_words = Text("point on a sphere")
|
|
sphere_words.move_to(circle_words, LEFT)
|
|
sphere_words.set_color(BLUE)
|
|
|
|
sphere = Sphere()
|
|
sphere.set_color(BLUE_D)
|
|
sphere.set_opacity(0.5)
|
|
sphere.set_shadow(0.5)
|
|
sphere.set_gloss(0.5)
|
|
sphere.set_reflectiveness(0.5)
|
|
sphere.replace(circle)
|
|
sphere.rotate(0.05 * PI)
|
|
sphere.rotate(0.4 * PI, LEFT)
|
|
sphere.sort_faces_back_to_front()
|
|
|
|
mesh = SurfaceMesh(sphere)
|
|
mesh.set_stroke(WHITE, 1, 0.5)
|
|
|
|
self.play(
|
|
FadeOut(dots),
|
|
FadeOut(circle, run_time=2),
|
|
ShowCreation(sphere, run_time=2),
|
|
ShowCreation(mesh, lag_ratio=0.1, run_time=2),
|
|
FadeOut(circle_words, UP),
|
|
FadeIn(sphere_words, UP),
|
|
)
|
|
|
|
dots.set_points([
|
|
random.choice(sphere.get_points())
|
|
for x in range(100)
|
|
])
|
|
dots.set_radius(0.25)
|
|
self.play(ShowCreation(dots, run_time=10, rate_func=linear))
|
|
|
|
# Ambiguity
|
|
underline = Underline(
|
|
subwords.get_part_by_tex("uniform"),
|
|
buff=0
|
|
)
|
|
underline.set_stroke(YELLOW, width=[0, 2, 2, 2, 0])
|
|
underline.scale(1.25)
|
|
underline.insert_n_curves(50)
|
|
question = Text("Is this well-defined?")
|
|
question.to_edge(RIGHT).shift(UP)
|
|
arrow = Arrow(underline, question, buff=0.1)
|
|
arrow.set_color(YELLOW)
|
|
|
|
turn_animation_into_updater(
|
|
ShowCreation(dots, run_time=10, rate_func=lambda t: 0.5 + 0.5 * t),
|
|
)
|
|
|
|
dots.clear_updaters()
|
|
dots.set_points(dots.get_points()[12:16])
|
|
dots.set_radius(0.5)
|
|
dots.add_updater(lambda m: m)
|
|
|
|
self.play(
|
|
ShowCreation(underline),
|
|
ShowCreation(arrow),
|
|
FadeIn(question, shift=0.5 * DR),
|
|
ShowCreation(dots, run_time=4, rate_func=linear)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class CoinFlips(Scene):
|
|
CONFIG = {
|
|
"random_seed": 2,
|
|
}
|
|
|
|
def construct(self):
|
|
n_rows = 15
|
|
n_cols = 40
|
|
heads = [bool(random.randint(0, 1)) for x in range(n_rows * n_cols)]
|
|
coins = VGroup(*(self.get_coin(h) for h in heads))
|
|
coins.arrange_in_grid(n_rows, n_cols, buff=SMALL_BUFF)
|
|
coins.set_width(FRAME_WIDTH - 0.5)
|
|
coins.to_edge(DOWN)
|
|
|
|
eq = OldTex(
|
|
"{\\# \\text{Heads} \\over \\# \\text{Flips}} = ",
|
|
"{Num \\over Den}", "=", "0.500",
|
|
isolate={"Num", "Den", "\\# \\text{Heads}"}
|
|
)
|
|
eq.set_color_by_tex("Heads", RED)
|
|
num = Integer(100, edge_to_fix=ORIGIN)
|
|
den = Integer(100, edge_to_fix=ORIGIN)
|
|
dec = DecimalNumber(0.5, num_decimal_places=3, edge_to_fix=LEFT)
|
|
num_i = eq.index_of_part_by_tex("Num")
|
|
den_i = eq.index_of_part_by_tex("Den")
|
|
dec_i = eq.index_of_part_by_tex("0.500")
|
|
num.replace(eq[num_i], dim_to_match=1)
|
|
den.replace(eq[den_i], dim_to_match=1)
|
|
dec.replace(eq[dec_i], dim_to_match=1)
|
|
eq.replace_submobject(num_i, num)
|
|
eq.replace_submobject(den_i, den)
|
|
eq.replace_submobject(dec_i, dec)
|
|
eq.to_edge(UP)
|
|
|
|
def update_eq(eq, alpha):
|
|
n_flips = max(int(alpha * len(heads)), 1)
|
|
n_heads = sum(heads[:n_flips])
|
|
num.set_value(n_heads).set_color(RED)
|
|
den.set_value(n_flips)
|
|
dec.set_value(n_heads / n_flips)
|
|
return eq
|
|
|
|
for sm in eq:
|
|
sm.add_updater(lambda m: m)
|
|
|
|
words = OldTexText("What does\\\\this approach?", font_size=36)
|
|
words.to_corner(UR)
|
|
arrow = Arrow(words, dec)
|
|
VGroup(words, arrow).set_color(YELLOW)
|
|
|
|
self.add(coins, *eq)
|
|
srf = squish_rate_func(smooth, 0.3, 0.4)
|
|
self.play(
|
|
ShowIncreasingSubsets(coins, rate_func=linear),
|
|
UpdateFromAlphaFunc(Mobject(), update_eq, rate_func=linear),
|
|
Write(words, rate_func=srf),
|
|
ShowCreation(arrow, rate_func=srf),
|
|
run_time=18
|
|
)
|
|
self.wait(2)
|
|
|
|
def get_coin(self, heads=True, radius=0.25):
|
|
circle = Dot(radius=radius)
|
|
circle.set_fill(RED_E if heads else BLUE_E)
|
|
circle.set_stroke(WHITE, 0.5, 0.5)
|
|
symbol = OldTex("H" if heads else "T")
|
|
symbol.set_height(radius)
|
|
symbol.move_to(circle)
|
|
return VGroup(circle, symbol)
|
|
|
|
|
|
class ChordsInSpaceWithCircle(RandomChordScene):
|
|
def construct(self):
|
|
# Introduce chords
|
|
n_lines = 500
|
|
big_circle = Circle(radius=FRAME_WIDTH + FRAME_HEIGHT)
|
|
lines = VGroup(*(
|
|
RadialPoint.get_random_chord(big_circle)
|
|
for x in range(n_lines)
|
|
))
|
|
lines.set_stroke(WHITE, 0.5, 0.5)
|
|
|
|
circle = Circle(radius=2)
|
|
circle.set_stroke(WHITE, 2)
|
|
|
|
triangle = Polygon(*(circle.pfp(a) for a in np.arange(0, 1, 1 / 3)))
|
|
triangle.rotate(-PI / 6, about_point=circle.get_center())
|
|
triangle.set_stroke(YELLOW, 3)
|
|
triangle = VGroup(
|
|
triangle.copy().set_stroke(BLACK, 8),
|
|
triangle.copy(),
|
|
)
|
|
|
|
self.play(Write(lines, lag_ratio=(1 / n_lines), run_time=6))
|
|
self.wait()
|
|
|
|
def get_chords():
|
|
return VGroup(*(
|
|
self.line_to_chord(line, circle)
|
|
for line in lines
|
|
))
|
|
|
|
chords = get_chords()
|
|
chords.save_state(RED)
|
|
chords.set_stroke(WHITE, 1)
|
|
self.play(
|
|
ShowCreation(circle),
|
|
VFadeIn(chords),
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(triangle, lag_ratio=0))
|
|
circle.add(triangle)
|
|
self.wait()
|
|
|
|
# Show colors
|
|
key = OldTexText(
|
|
"Blue $\\Rightarrow$ Chord > \\text{Triangle side}\\\\"
|
|
"Red $\\Rightarrow$ Chord < \\text{Triangle side}\\\\",
|
|
tex_to_color_map={
|
|
"Blue": BLUE,
|
|
"Red": RED,
|
|
}
|
|
)
|
|
key.to_edge(UP)
|
|
key.set_backstroke(width=5)
|
|
key[len(key) // 2:].align_to(key[:len(key) // 2], RIGHT)
|
|
|
|
self.play(
|
|
Restore(chords),
|
|
FadeIn(key),
|
|
)
|
|
self.wait(2)
|
|
|
|
# Move around
|
|
chords.add_updater(lambda m: m.become(get_chords()))
|
|
self.add(chords, circle)
|
|
self.play(circle.animate.to_edge(RIGHT), run_time=6)
|
|
self.play(circle.animate.to_edge(LEFT), run_time=6)
|
|
self.wait()
|
|
self.play(
|
|
circle.animate.set_height(7, about_edge=LEFT),
|
|
FadeOut(key, rate_func=squish_rate_func(smooth, 0, 0.5)),
|
|
run_time=4
|
|
)
|
|
self.play(circle.animate.set_height(4, about_edge=RIGHT), run_time=4)
|
|
|
|
# Show center point method
|
|
big_circle = Circle(radius=3.25)
|
|
big_circle.set_stroke(GREY_B, 2)
|
|
big_circle.to_edge(DOWN)
|
|
|
|
title = Text("Random center point method")
|
|
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
|
|
chords.clear_updaters()
|
|
self.play(
|
|
FadeOut(chords),
|
|
FadeOut(lines),
|
|
FadeIn(big_circle),
|
|
FadeIn(title),
|
|
circle.animate.set_height(0.5).to_corner(DR),
|
|
)
|
|
triangle.set_stroke(width=2)
|
|
|
|
n_lines = 1000
|
|
lines.become(VGroup(*(
|
|
CenterPoint.get_random_chord(big_circle)
|
|
for x in range(n_lines)
|
|
)))
|
|
lines.set_stroke(WHITE, self.chord_width, self.chord_opacity)
|
|
flash_lines = lines.copy()
|
|
flash_lines.set_stroke(width=3, opacity=1)
|
|
indicators = Group(*map(CenterPoint.get_method_indicator, lines))
|
|
|
|
self.play(
|
|
ShowIncreasingSubsets(lines),
|
|
ShowSubmobjectsOneByOne(flash_lines),
|
|
ShowSubmobjectsOneByOne(indicators),
|
|
rate_func=linear,
|
|
run_time=8,
|
|
)
|
|
self.play(
|
|
FadeOut(indicators[-1]),
|
|
FadeOut(flash_lines[-1]),
|
|
)
|
|
self.wait()
|
|
|
|
# Put circle back inside
|
|
self.play(
|
|
circle.animate.set_height(2).move_to(big_circle),
|
|
run_time=1
|
|
)
|
|
chords.become(get_chords().set_stroke(opacity=0.7))
|
|
self.add(chords, circle)
|
|
self.play(VFadeIn(chords))
|
|
self.wait()
|
|
chords.add_updater(lambda m: m.become(get_chords().set_stroke(opacity=0.7)))
|
|
self.play(
|
|
circle.animate.move_to(big_circle, RIGHT),
|
|
run_time=10,
|
|
rate_func=there_and_back,
|
|
)
|
|
self.wait()
|
|
|
|
def line_to_chord(self, line, circle, stroke_width=1):
|
|
r = circle.get_radius()
|
|
tangent = normalize(line.get_vector())
|
|
normal = rotate_vector(tangent, PI / 2)
|
|
center = circle.get_center()
|
|
d1 = np.dot(line.get_start() - center, normal)
|
|
if d1 > r:
|
|
return VectorizedPoint(center + r * normal)
|
|
d2 = np.sqrt(r**2 - d1**2)
|
|
chord = Line(
|
|
center + d1 * normal - d2 * tangent,
|
|
center + d1 * normal + d2 * tangent,
|
|
)
|
|
chord.set_stroke(
|
|
(BLUE if chord.get_length() > math.sqrt(3) * r else RED),
|
|
width=stroke_width,
|
|
)
|
|
return chord
|
|
|
|
|
|
class TransitiveSymmetries(Scene):
|
|
def construct(self):
|
|
circle = Circle(radius=3)
|
|
circle.set_stroke(GREY_B, 2)
|
|
circle.rotate(PI / 2)
|
|
|
|
dots = DotCloud([circle.pfp(a) for a in np.linspace(0, 1, 49)])
|
|
dots.set_glow_factor(5)
|
|
dots.set_radius(0.5)
|
|
dots.set_color(WHITE)
|
|
dots.set_opacity(0.5)
|
|
|
|
dot = dots.copy()
|
|
dot.set_points([dots.get_points()[0]])
|
|
dot.set_color(YELLOW)
|
|
dot.set_opacity(1)
|
|
|
|
top_words = Text("By acting on any one point...")
|
|
top_words.match_x(dot)
|
|
top_words.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
low_words = Text("...you can map\nto any other")
|
|
low_words.to_edge(RIGHT).shift(2 * DOWN)
|
|
|
|
self.add(circle, top_words, dot)
|
|
dots.add_updater(lambda m: m)
|
|
self.play(
|
|
Rotate(
|
|
Group(circle, dot), TAU,
|
|
about_point=circle.get_center(),
|
|
),
|
|
ShowCreation(
|
|
dots,
|
|
rate_func=lambda a: smooth(a * (1 - 1 / 48) + 1 / 48)
|
|
),
|
|
Write(low_words, rate_func=squish_rate_func(smooth, 0.3, 0.5)),
|
|
run_time=10,
|
|
)
|
|
|
|
|
|
class NonTransitive(Scene):
|
|
def construct(self):
|
|
circle = Circle(radius=3)
|
|
circle.set_stroke(GREY_B, 2)
|
|
|
|
words = OldTexText(
|
|
"Rotational symmetries do \\emph{not} act\\\\"
|
|
"transitively on the space of all chords",
|
|
tex_to_color_map={"\\emph{not}": RED},
|
|
)
|
|
words.to_edge(UP, MED_SMALL_BUFF)
|
|
circle.next_to(words, DOWN, MED_SMALL_BUFF)
|
|
|
|
chord1 = Line(circle.pfp(0.4), circle.pfp(0.5))
|
|
chord1.set_stroke(RED, 3)
|
|
chord1_shadow = chord1.copy().set_stroke(opacity=0.5)
|
|
chord2 = Line(circle.pfp(0.8), circle.pfp(0.25))
|
|
chord2.set_stroke(BLUE, 3)
|
|
|
|
left_words = Text("No action on\nthis chord...")
|
|
right_words = Text("...will ever map\nto this chord.")
|
|
left_words.to_edge(LEFT).shift(UP)
|
|
right_words.to_edge(RIGHT).shift(DOWN)
|
|
|
|
left_arrow = Arrow(left_words, chord1.get_center(), buff=0.1)
|
|
right_arrow = Arrow(right_words.get_left(), chord2.get_center(), buff=0.1)
|
|
|
|
VGroup(left_words, left_arrow).set_color(RED)
|
|
VGroup(right_words, right_arrow).set_color(BLUE)
|
|
|
|
chords = VGroup(*(
|
|
RadialPoint.get_random_chord(circle)
|
|
for x in range(100)
|
|
))
|
|
chords.set_stroke(WHITE, 1, 0.5)
|
|
|
|
group = VGroup(circle, chords)
|
|
|
|
self.add(words)
|
|
self.play(
|
|
Rotate(group, TAU, about_point=circle.get_center(), run_time=6)
|
|
)
|
|
self.wait()
|
|
|
|
self.add(chord1_shadow, chord1)
|
|
self.play(
|
|
FadeOut(chords),
|
|
ShowCreation(chord1_shadow),
|
|
ShowCreation(chord1),
|
|
FadeIn(left_words),
|
|
ShowCreation(left_arrow),
|
|
)
|
|
group = VGroup(circle, chord1)
|
|
self.add(group, left_arrow)
|
|
self.play(
|
|
Rotate(
|
|
group, TAU,
|
|
about_point=circle.get_center(),
|
|
),
|
|
FadeIn(
|
|
VGroup(right_words, right_arrow, chord2),
|
|
rate_func=squish_rate_func(smooth, 0.3, 0.4),
|
|
),
|
|
run_time=10,
|
|
)
|
|
|
|
# Embed
|
|
self.embed()
|
|
|
|
|
|
class RandomSpherePoint(Scene):
|
|
def construct(self):
|
|
# Setup
|
|
frame = self.camera.frame
|
|
frame.reorient(-20, 70)
|
|
frame.add_updater(lambda m, dt: m.increment_theta(0.015 * dt))
|
|
|
|
grid = NumberPlane(
|
|
(-6, 6), (-4, 4),
|
|
background_line_style={
|
|
"stroke_color": GREY_B,
|
|
"stroke_width": 1,
|
|
"stroke_opacity": 0.5,
|
|
},
|
|
axis_config={
|
|
"stroke_width": 1,
|
|
}
|
|
)
|
|
grid.scale(2)
|
|
plane = Rectangle()
|
|
plane.set_stroke(width=0)
|
|
plane.set_gloss(0.5)
|
|
plane.set_fill(GREY_C, 0.5)
|
|
plane.replace(grid, stretch=True)
|
|
plane.add(grid)
|
|
plane.shift(2 * IN)
|
|
self.add(plane)
|
|
|
|
sphere_radius = 2
|
|
sphere = Sphere(radius=sphere_radius)
|
|
sphere.set_color(BLUE_E)
|
|
sphere.set_opacity(1.0)
|
|
sphere.move_to(1.5 * OUT)
|
|
|
|
mesh = SurfaceMesh(sphere)
|
|
mesh.set_stroke(width=0.5, opacity=0.5)
|
|
mesh.apply_depth_test()
|
|
mesh_shadow = mesh.copy().set_stroke(opacity=0.1)
|
|
mesh_shadow.deactivate_depth_test()
|
|
|
|
sphere = Group(sphere, mesh, mesh_shadow)
|
|
self.add(*sphere)
|
|
|
|
# Random point
|
|
words = OldTexText("Choose a random$^{*}$\\\\point on a sphere")
|
|
words.fix_in_frame()
|
|
words.to_corner(UL)
|
|
|
|
technicality = OldTexText(
|
|
"$^{*}$From a distribution that's\\\\",
|
|
"invariant under rotational symmetries.",
|
|
font_size=30
|
|
)
|
|
technicality[1].set_color(YELLOW)
|
|
technicality.fix_in_frame()
|
|
technicality.to_corner(UR)
|
|
technicality.to_edge(RIGHT, buff=MED_SMALL_BUFF)
|
|
|
|
def random_sphere_point():
|
|
p = [1, 1, 1]
|
|
while get_norm(p) > 1:
|
|
p = np.random.uniform(-1, 1, 3)
|
|
p = sphere_radius * normalize(p)
|
|
return p + sphere.get_center()
|
|
|
|
dot = TrueDot()
|
|
dot.set_radius(0.5)
|
|
dot.set_color(YELLOW)
|
|
dot.set_glow_factor(5)
|
|
dot.move_to(random_sphere_point())
|
|
|
|
self.add(words)
|
|
dot.set_radius(0)
|
|
self.play(dot.animate.set_radius(0.5))
|
|
|
|
for x in range(11):
|
|
dot.move_to(random_sphere_point())
|
|
dot.set_opacity(random.choice([0.25, 1.0]))
|
|
if x == 5:
|
|
self.play(FadeIn(technicality), run_time=0.5)
|
|
else:
|
|
self.wait(0.5)
|
|
self.play(FadeOut(dot))
|
|
|
|
# Little patch
|
|
band = sphere[0].copy()
|
|
band.pointwise_become_partial(sphere[0], 0.7, 0.8)
|
|
patch = band.copy()
|
|
patch.pointwise_become_partial(band, 0.7, 0.75, axis=0)
|
|
patch.set_color(TEAL)
|
|
patch.set_opacity(0.8)
|
|
patch.deactivate_depth_test()
|
|
|
|
dot.move_to(patch)
|
|
|
|
def show_dots_in_patch(n=2000):
|
|
dots = DotCloud([
|
|
random_sphere_point()
|
|
for x in range(n)
|
|
])
|
|
dots.set_color(WHITE)
|
|
dots.set_radius(0.02)
|
|
dots.set_glow_factor(1)
|
|
dots.set_opacity([
|
|
0.25 if point[1] > 0 else 1.0
|
|
for point in dots.get_points()
|
|
])
|
|
dots.add_updater(lambda m: m)
|
|
self.play(ShowCreation(dots, run_time=6, rate_func=linear))
|
|
self.wait()
|
|
self.play(FadeOut(dots))
|
|
|
|
self.play(GrowFromCenter(patch))
|
|
self.wait()
|
|
show_dots_in_patch()
|
|
self.wait()
|
|
|
|
# patch_copy = patch.copy()
|
|
self.play(*(
|
|
Rotate(
|
|
sm, PI / 3, axis=OUT + RIGHT,
|
|
about_point=sphere.get_center(),
|
|
run_time=2,
|
|
)
|
|
for sm in (*sphere, patch)
|
|
))
|
|
show_dots_in_patch()
|
|
self.wait()
|
|
|
|
# Embed
|
|
self.embed()
|
|
|
|
|
|
class CorrectionInsert(Scene):
|
|
def construct(self):
|
|
# Words
|
|
title = OldTexText("Looking for an unambiguous ``uniform'' distribution?")
|
|
title.to_edge(UP, buff=0.25)
|
|
kw = dict(
|
|
# font_size=36,
|
|
# color=GREY_A,
|
|
t2c={"compact": YELLOW, "transitively": BLUE},
|
|
t2s={"compact": ITALIC},
|
|
)
|
|
conditions = VGroup(
|
|
Text("1) Find a symmetry which acts transitively", **kw),
|
|
Text("2) The space must be compact", **kw),
|
|
)
|
|
conditions.arrange(DOWN, aligned_edge=LEFT)
|
|
conditions.next_to(title, DOWN, MED_LARGE_BUFF)
|
|
|
|
conditions.to_edge(UP)
|
|
|
|
# Add shapes
|
|
radius = 1.5
|
|
|
|
interval = UnitInterval(width=4)
|
|
interval.add_numbers([0, 0.5, 1.0], num_decimal_places=1)
|
|
|
|
circle = Circle(radius=radius)
|
|
circle.set_stroke(GREY_B, 2)
|
|
|
|
sphere = Sphere(radius=radius)
|
|
sphere.set_color(BLUE_D, 1)
|
|
sphere.set_opacity(0.5)
|
|
mesh = SurfaceMesh(sphere)
|
|
mesh.set_stroke(width=0.5)
|
|
mesh_shadow = mesh.copy().set_stroke(opacity=0.35)
|
|
sphere_group = Group(mesh_shadow, sphere, mesh)
|
|
sphere_group.rotate(80 * DEGREES, LEFT)
|
|
sphere_group.rotate(2.5 * DEGREES, OUT)
|
|
sphere.sort_faces_back_to_front()
|
|
|
|
shapes = Group(interval, circle, sphere_group)
|
|
shapes.arrange(RIGHT, buff=LARGE_BUFF)
|
|
shapes.set_y(-1)
|
|
|
|
# Dots
|
|
interval_dots = DotCloud([interval.pfp(random.random()) for n in range(100)])
|
|
circle_dots = DotCloud([circle.pfp(random.random()) for n in range(100)])
|
|
sphere_dots = DotCloud([
|
|
sphere.pfp(math.acos(random.random()) / PI)
|
|
for n in range(300)
|
|
])
|
|
|
|
all_dots = Group(interval_dots, circle_dots, sphere_dots)
|
|
for dots in all_dots:
|
|
dots.set_glow_factor(5)
|
|
dots.set_radius(0.1)
|
|
dots.set_color(YELLOW)
|
|
dots.add_updater(lambda m: m)
|
|
sphere_dots.set_opacity(np.random.choice([1, 0.2], sphere_dots.get_num_points()))
|
|
circle_dots.set_radius(0.15)
|
|
|
|
# Animations
|
|
self.add(conditions[0])
|
|
self.add(shapes)
|
|
self.add(*dots)
|
|
|
|
self.play(*(ShowCreation(dots, rate_func=linear, run_time=15) for dots in all_dots))
|
|
self.wait(4)
|
|
self.play(Write(conditions[1][:2]))
|
|
self.wait(2)
|
|
self.play(Write(conditions[1][2:]))
|
|
self.wait()
|
|
|
|
# Compact
|
|
rects = Rectangle(height=4.75, width=6).get_grid(1, 2, buff=0.5)
|
|
rects.set_stroke(GREY, 1)
|
|
rects.to_edge(DOWN)
|
|
|
|
rect_titles = VGroup(Text("Compact"), Text("Not compact"))
|
|
for rt, r, color in zip(rect_titles, rects, [YELLOW, RED]):
|
|
rt.scale(0.7)
|
|
rt.next_to(r, UP, buff=SMALL_BUFF)
|
|
rt.set_color(color)
|
|
|
|
compact_spaces = Group(
|
|
Group(interval, interval_dots),
|
|
Group(circle, circle_dots),
|
|
Group(*sphere_group, sphere_dots)
|
|
)
|
|
compact_spaces.generate_target()
|
|
compact_spaces.target.arrange(DOWN)
|
|
compact_spaces.target.set_height(0.9 * rects[0].get_height())
|
|
compact_spaces.target.move_to(rects[0])
|
|
|
|
movers = []
|
|
for cs, cst in zip(compact_spaces, compact_spaces.target):
|
|
for m, mt in zip(cs, cst):
|
|
m.generate_target()
|
|
m.target.replace(mt)
|
|
movers.append(m)
|
|
|
|
self.play(
|
|
*map(MoveToTarget, movers),
|
|
*map(ShowCreation, rects),
|
|
*map(FadeIn, rect_titles),
|
|
)
|
|
self.wait()
|
|
|
|
# Non-compact
|
|
reals = NumberLine((-5, 5), include_tip=True)
|
|
reals.ticks.remove(reals.ticks[0])
|
|
reals.add(reals.copy().flip())
|
|
reals.add_numbers(font_size=20)
|
|
reals.set_width(0.9 * rects[1].get_width())
|
|
reals.next_to(rects[1].get_top(), DOWN, buff=LARGE_BUFF)
|
|
reals_label = Text("Real number line", font_size=30)
|
|
reals_label.next_to(reals, UP, SMALL_BUFF)
|
|
|
|
plane = Square(4)
|
|
plane.set_stroke(width=0)
|
|
plane.set_fill(GREY_D, 1)
|
|
plane.set_gloss(0.5)
|
|
plane.set_reflectiveness(0.4)
|
|
arrows = VGroup(*(
|
|
Arrow(v, 2 * v, stroke_width=2)
|
|
for v in compass_directions(8)
|
|
))
|
|
group = VGroup(plane, arrows)
|
|
group.rotate(80 * DEGREES, LEFT)
|
|
group.set_width(4.5)
|
|
group.next_to(reals, DOWN, buff=1.5)
|
|
group.shift(0.25 * LEFT)
|
|
|
|
self.play(
|
|
ShowCreation(reals),
|
|
FadeIn(reals_label),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(plane),
|
|
*map(ShowCreation, arrows),
|
|
)
|
|
self.wait(3)
|