mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 17:29:06 +08:00
3523 lines
100 KiB
Python
3523 lines
100 KiB
Python
from manimlib.imports import *
|
|
import json
|
|
import numbers
|
|
|
|
|
|
OUTPUT_DIRECTORY = "spirals"
|
|
INV_113_MOD_710 = 377 # Inverse of 113 mode 710
|
|
INV_7_MOD_44 = 19
|
|
|
|
|
|
def is_prime(n):
|
|
if n < 2:
|
|
return False
|
|
for k in range(2, int(np.sqrt(n)) + 1):
|
|
if n % k == 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
def generate_prime_list(*args):
|
|
if len(args) == 1:
|
|
start, stop = 2, args[0]
|
|
elif len(args) == 2:
|
|
start, stop = args
|
|
start = max(start, 2)
|
|
else:
|
|
raise TypeError("generate_prime_list takes 1 or 2 arguments")
|
|
|
|
result = [
|
|
n for n in range(start, stop)
|
|
if is_prime(n)
|
|
]
|
|
return result
|
|
|
|
|
|
def get_gcd(x, y):
|
|
while y > 0:
|
|
x, y = y, x % y
|
|
return x
|
|
|
|
|
|
def read_in_primes(max_N=None):
|
|
if max_N is None:
|
|
max_N = int(1e7)
|
|
|
|
if max_N < 1e5:
|
|
file = "primes_1e5.json"
|
|
elif max_N < 1e6:
|
|
file = "primes_1e6.json"
|
|
else:
|
|
file = "primes_1e7.json"
|
|
|
|
with open(os.path.join("assets", file)) as fp:
|
|
primes = np.array(json.load(fp))
|
|
return primes[primes <= max_N]
|
|
|
|
|
|
class SpiralScene(MovingCameraScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"number_line_config": {
|
|
"stroke_width": 1.5,
|
|
}
|
|
},
|
|
"default_dot_color": TEAL,
|
|
"p_spiral_width": 6,
|
|
}
|
|
|
|
def setup(self):
|
|
super().setup()
|
|
self.axes = Axes(**self.axes_config)
|
|
self.add(self.axes)
|
|
|
|
def get_v_spiral(self, sequence, axes=None, box_width=None):
|
|
if axes is None:
|
|
axes = self.axes
|
|
if box_width is None:
|
|
unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)),
|
|
box_width = max(
|
|
0.2 / (-np.log10(unit) + 1),
|
|
0.02,
|
|
)
|
|
|
|
return VGroup(*[
|
|
Square(
|
|
side_length=box_width,
|
|
fill_color=self.default_dot_color,
|
|
fill_opacity=1,
|
|
stroke_width=0,
|
|
).move_to(self.get_polar_point(n, n, axes))
|
|
for n in sequence
|
|
])
|
|
|
|
def get_p_spiral(self, sequence, axes=None):
|
|
if axes is None:
|
|
axes = self.axes
|
|
result = PMobject(
|
|
color=self.default_dot_color,
|
|
stroke_width=self.p_spiral_width,
|
|
)
|
|
result.add_points([
|
|
self.get_polar_point(n, n, axes)
|
|
for n in sequence
|
|
])
|
|
return result
|
|
|
|
def get_prime_v_spiral(self, max_N, **kwargs):
|
|
primes = read_in_primes(max_N)
|
|
return self.get_v_spiral(primes, **kwargs)
|
|
|
|
def get_prime_p_spiral(self, max_N, **kwargs):
|
|
primes = read_in_primes(max_N)
|
|
return self.get_p_spiral(primes, **kwargs)
|
|
|
|
def get_polar_point(self, r, theta, axes=None):
|
|
if axes is None:
|
|
axes = self.axes
|
|
return axes.c2p(r * np.cos(theta), r * np.sin(theta))
|
|
|
|
def set_scale(self, scale,
|
|
axes=None,
|
|
spiral=None,
|
|
to_shrink=None,
|
|
min_box_width=0.05,
|
|
target_p_spiral_width=None,
|
|
added_anims=[],
|
|
run_time=3):
|
|
if axes is None:
|
|
axes = self.axes
|
|
if added_anims is None:
|
|
added_anims = []
|
|
sf = self.get_scale_factor(scale, axes)
|
|
|
|
anims = []
|
|
for mob in [axes, spiral, to_shrink]:
|
|
if mob is None:
|
|
continue
|
|
mob.generate_target()
|
|
mob.target.scale(sf, about_point=ORIGIN)
|
|
if mob is spiral:
|
|
if isinstance(mob, VMobject):
|
|
old_width = mob[0].get_width()
|
|
for submob in mob.target:
|
|
submob.set_width(max(
|
|
old_width * sf,
|
|
min_box_width,
|
|
))
|
|
elif isinstance(mob, PMobject):
|
|
if target_p_spiral_width is not None:
|
|
mob.target.set_stroke_width(target_p_spiral_width)
|
|
anims.append(MoveToTarget(mob))
|
|
anims += added_anims
|
|
|
|
if run_time == 0:
|
|
for anim in anims:
|
|
anim.begin()
|
|
anim.update(1)
|
|
anim.finish()
|
|
else:
|
|
self.play(
|
|
*anims,
|
|
run_time=run_time,
|
|
rate_func=lambda t: interpolate(
|
|
smooth(t),
|
|
smooth(t)**(sf**(0.5)),
|
|
t,
|
|
)
|
|
)
|
|
|
|
def get_scale_factor(self, target_scale, axes=None):
|
|
if axes is None:
|
|
axes = self.axes
|
|
unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0))
|
|
return 1 / (target_scale * unit)
|
|
|
|
def get_labels(self, sequence, scale_func=np.sqrt):
|
|
labels = VGroup()
|
|
for n in sequence:
|
|
label = Integer(n)
|
|
label.set_stroke(width=0, background=True)
|
|
label.scale(scale_func(n))
|
|
label.next_to(
|
|
self.get_polar_point(n, n), UP,
|
|
buff=0.5 * label.get_height(),
|
|
)
|
|
labels.add(label)
|
|
return labels
|
|
|
|
def get_prime_labels(self, max_N):
|
|
primes = read_in_primes(max_N)
|
|
return self.get_labels(primes)
|
|
|
|
|
|
# Scenes
|
|
|
|
class AltTitle(Scene):
|
|
def construct(self):
|
|
title_text = """
|
|
How pretty but pointless patterns\\\\
|
|
in polar plots of primes\\\\
|
|
prompt pretty important ponderings\\\\
|
|
on properties of those primes.
|
|
"""
|
|
words = [w + " " for w in title_text.split(" ") if w]
|
|
title = TextMobject(*words)
|
|
title.set_width(FRAME_WIDTH - 1)
|
|
|
|
title[2:5].set_color(TEAL)
|
|
title[12:15].set_color(YELLOW)
|
|
title.set_stroke(BLACK, 5, background=True)
|
|
|
|
image = ImageMobject("PrimeSpiral")
|
|
image.set_height(FRAME_HEIGHT)
|
|
rect = FullScreenFadeRectangle(fill_opacity=0.25)
|
|
|
|
self.add(image, rect)
|
|
|
|
for word in title:
|
|
self.play(
|
|
FadeIn(
|
|
word, run_time=0.05 * len(word),
|
|
lag_ratio=0.4,
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class HoldUpMathExchange(TeacherStudentsScene):
|
|
def construct(self):
|
|
title = TextMobject("Mathematics Stack Exchange")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
|
|
self.add(title)
|
|
self.play(self.teacher.change, "raise_right_hand", ORIGIN),
|
|
self.change_all_student_modes("thinking", look_at_arg=ORIGIN)
|
|
self.wait(3)
|
|
self.change_all_student_modes("confused", look_at_arg=ORIGIN)
|
|
self.wait(3)
|
|
|
|
|
|
class MathExchangeNames(Scene):
|
|
def construct(self):
|
|
names = VGroup(
|
|
TextMobject("dwymark"),
|
|
TextMobject("Greg Martin"),
|
|
)
|
|
names.arrange(DOWN, buff=1)
|
|
for name in names:
|
|
self.play(FadeInFrom(name, RIGHT))
|
|
self.wait()
|
|
|
|
|
|
class MathExchange(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class PrimesAndPi(Scene):
|
|
def construct(self):
|
|
self.show_primes()
|
|
self.show_rational_approximations()
|
|
|
|
def show_primes(self):
|
|
n_rows = 10
|
|
n_cols = 10
|
|
matrix = IntegerMatrix([
|
|
[n_cols * x + y for y in range(n_cols)]
|
|
for x in range(n_rows)
|
|
])
|
|
numbers = matrix.get_entries()
|
|
primes = VGroup(*filter(
|
|
lambda m: is_prime(m.get_value()),
|
|
numbers,
|
|
))
|
|
non_primes = VGroup(*filter(
|
|
lambda m: not is_prime(m.get_value()),
|
|
numbers
|
|
))
|
|
|
|
self.add(numbers)
|
|
|
|
self.play(
|
|
LaggedStart(*[
|
|
ApplyFunction(
|
|
lambda m: m.set_color(TEAL).scale(1.2),
|
|
prime
|
|
)
|
|
for prime in primes
|
|
]),
|
|
non_primes.set_opacity, 0.25,
|
|
run_time=2,
|
|
)
|
|
self.wait()
|
|
|
|
self.numbers = numbers
|
|
|
|
def show_rational_approximations(self):
|
|
numbers = self.numbers
|
|
|
|
approxs = TexMobject(
|
|
"{22 \\over 7} &=", "{:.12}\\dots\\\\".format(22 / 7),
|
|
"{355 \\over 113} &=", "{:.12}\\dots\\\\".format(355 / 113),
|
|
"\\pi &=", "{:.12}\\dots\\\\".format(PI),
|
|
)
|
|
approxs[:2].shift(MED_LARGE_BUFF * UP)
|
|
approxs[-2:].shift(MED_LARGE_BUFF * DOWN)
|
|
approxs[-2:].set_color(YELLOW)
|
|
approxs[1][:4].set_color(YELLOW)
|
|
approxs[3][:8].set_color(YELLOW)
|
|
approxs.scale(1.5)
|
|
|
|
randy = Randolph(color=YELLOW, height=1)
|
|
randy.move_to(approxs[-2][0], RIGHT)
|
|
approxs[-2][0].set_opacity(0)
|
|
|
|
self.play(
|
|
LaggedStartMap(FadeOutAndShiftDown, numbers),
|
|
LaggedStartMap(FadeIn, approxs),
|
|
FadeIn(randy)
|
|
)
|
|
self.play(Blink(randy))
|
|
self.play(randy.change, "pondering", UR)
|
|
self.wait()
|
|
|
|
|
|
class RefresherOnPolarCoordinates(MovingCameraScene):
|
|
CONFIG = {
|
|
"x_color": GREEN,
|
|
"y_color": RED,
|
|
"r_color": YELLOW,
|
|
"theta_color": LIGHT_PINK,
|
|
}
|
|
|
|
def construct(self):
|
|
self.show_xy_coordinates()
|
|
self.transition_to_polar_grid()
|
|
self.show_polar_coordinates()
|
|
|
|
self.show_all_nn_tuples()
|
|
|
|
def show_xy_coordinates(self):
|
|
plane = NumberPlane()
|
|
plane.add_coordinates()
|
|
|
|
x = 3 * np.cos(PI / 6)
|
|
y = 3 * np.sin(PI / 6)
|
|
|
|
point = plane.c2p(x, y)
|
|
xp = plane.c2p(x, 0)
|
|
origin = plane.c2p(0, 0)
|
|
|
|
x_color = self.x_color
|
|
y_color = self.y_color
|
|
|
|
x_line = Line(origin, xp, color=x_color)
|
|
y_line = Line(xp, point, color=y_color)
|
|
|
|
dot = Dot(point)
|
|
|
|
coord_label = self.get_coord_label(0, 0, x_color, y_color)
|
|
x_coord = coord_label.x_coord
|
|
y_coord = coord_label.y_coord
|
|
|
|
coord_label.next_to(dot, UR, SMALL_BUFF)
|
|
|
|
x_brace = Brace(x_coord, UP)
|
|
y_brace = Brace(y_coord, UP)
|
|
x_brace.add(x_brace.get_tex("x").set_color(x_color))
|
|
y_brace.add(y_brace.get_tex("y").set_color(y_color))
|
|
x_brace.add_updater(lambda m: m.next_to(x_coord, UP, SMALL_BUFF))
|
|
y_brace.add_updater(lambda m: m.next_to(y_coord, UP, SMALL_BUFF))
|
|
|
|
self.add(plane)
|
|
self.add(dot, coord_label)
|
|
self.add(x_brace, y_brace)
|
|
|
|
coord_label.add_updater(
|
|
lambda m: m.next_to(dot, UR, SMALL_BUFF)
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(x_line),
|
|
ChangeDecimalToValue(x_coord, x),
|
|
UpdateFromFunc(
|
|
dot,
|
|
lambda d: d.move_to(x_line.get_end()),
|
|
),
|
|
run_time=2,
|
|
)
|
|
self.play(
|
|
ShowCreation(y_line),
|
|
ChangeDecimalToValue(y_coord, y),
|
|
UpdateFromFunc(
|
|
dot,
|
|
lambda d: d.move_to(y_line.get_end()),
|
|
),
|
|
run_time=2,
|
|
)
|
|
self.wait()
|
|
|
|
self.xy_coord_mobjects = VGroup(
|
|
x_line, y_line, coord_label,
|
|
x_brace, y_brace,
|
|
)
|
|
self.plane = plane
|
|
self.dot = dot
|
|
|
|
def transition_to_polar_grid(self):
|
|
self.polar_grid = self.get_polar_grid()
|
|
self.add(self.polar_grid, self.dot)
|
|
self.play(
|
|
FadeOut(self.xy_coord_mobjects),
|
|
FadeOut(self.plane),
|
|
ShowCreation(self.polar_grid, run_time=2),
|
|
)
|
|
self.wait()
|
|
|
|
def show_polar_coordinates(self):
|
|
dot = self.dot
|
|
plane = self.plane
|
|
origin = plane.c2p(0, 0)
|
|
|
|
r_color = self.r_color
|
|
theta_color = self.theta_color
|
|
|
|
r_line = Line(origin, dot.get_center())
|
|
r_line.set_color(r_color)
|
|
r_value = r_line.get_length()
|
|
theta_value = r_line.get_angle()
|
|
|
|
coord_label = self.get_coord_label(r_value, theta_value, r_color, theta_color)
|
|
r_coord = coord_label.x_coord
|
|
theta_coord = coord_label.y_coord
|
|
|
|
coord_label.add_updater(lambda m: m.next_to(dot, UP, buff=SMALL_BUFF))
|
|
r_coord.add_updater(lambda d: d.set_value(
|
|
get_norm(dot.get_center())
|
|
))
|
|
theta_coord.add_background_rectangle()
|
|
theta_coord.add_updater(lambda d: d.set_value(
|
|
(angle_of_vector(dot.get_center()) % TAU)
|
|
))
|
|
coord_label[-1].add_updater(
|
|
lambda m: m.next_to(theta_coord, RIGHT, SMALL_BUFF)
|
|
)
|
|
|
|
non_coord_parts = VGroup(*[
|
|
part
|
|
for part in coord_label
|
|
if part not in [r_coord, theta_coord]
|
|
])
|
|
|
|
r_label = TexMobject("r")
|
|
r_label.set_color(r_color)
|
|
r_label.add_updater(lambda m: m.next_to(r_coord, UP))
|
|
theta_label = TexMobject("\\theta")
|
|
theta_label.set_color(theta_color)
|
|
theta_label.add_updater(lambda m: m.next_to(theta_coord, UP))
|
|
|
|
r_coord_copy = r_coord.copy()
|
|
r_coord_copy.add_updater(
|
|
lambda m: m.next_to(r_line.get_center(), UL, buff=0)
|
|
)
|
|
|
|
degree_label = DecimalNumber(0, num_decimal_places=1, unit="^\\circ")
|
|
arc = Arc(radius=1, angle=theta_value)
|
|
arc.set_color(theta_color)
|
|
degree_label.set_color(theta_color)
|
|
|
|
# Show r
|
|
self.play(
|
|
ShowCreation(r_line, run_time=2),
|
|
ChangeDecimalToValue(r_coord_copy, r_value, run_time=2),
|
|
VFadeIn(r_coord_copy, run_time=0.5),
|
|
)
|
|
r_coord.set_value(r_value)
|
|
self.add(non_coord_parts, r_coord_copy)
|
|
self.play(
|
|
FadeIn(non_coord_parts),
|
|
ReplacementTransform(r_coord_copy, r_coord),
|
|
FadeInFromDown(r_label),
|
|
)
|
|
self.wait()
|
|
|
|
# Show theta
|
|
degree_label.next_to(arc.get_start(), UR, SMALL_BUFF)
|
|
line = r_line.copy()
|
|
line.rotate(-theta_value, about_point=ORIGIN)
|
|
line.set_color(theta_color)
|
|
self.play(
|
|
ShowCreation(arc),
|
|
Rotate(line, theta_value, about_point=ORIGIN),
|
|
VFadeInThenOut(line),
|
|
ChangeDecimalToValue(degree_label, theta_value / DEGREES),
|
|
)
|
|
self.play(
|
|
degree_label.scale, 0.9,
|
|
degree_label.move_to, theta_coord,
|
|
FadeInFromDown(theta_label),
|
|
)
|
|
self.wait()
|
|
|
|
degree_cross = Cross(degree_label)
|
|
radians_word = TextMobject("in radians")
|
|
radians_word.scale(0.9)
|
|
radians_word.set_color(theta_color)
|
|
radians_word.add_background_rectangle()
|
|
radians_word.add_updater(
|
|
lambda m: m.next_to(theta_label, RIGHT, aligned_edge=DOWN)
|
|
)
|
|
|
|
self.play(ShowCreation(degree_cross))
|
|
self.play(
|
|
FadeOutAndShift(
|
|
VGroup(degree_label, degree_cross),
|
|
DOWN
|
|
),
|
|
FadeIn(theta_coord)
|
|
)
|
|
self.play(FadeIn(radians_word))
|
|
self.wait()
|
|
|
|
# Move point around
|
|
r_line.add_updater(
|
|
lambda l: l.put_start_and_end_on(ORIGIN, dot.get_center())
|
|
)
|
|
theta_tracker = ValueTracker(0)
|
|
theta_tracker.add_updater(
|
|
lambda m: m.set_value(r_line.get_angle() % TAU)
|
|
)
|
|
self.add(theta_tracker)
|
|
arc.add_updater(
|
|
lambda m: m.become(
|
|
self.get_arc(theta_tracker.get_value())
|
|
)
|
|
)
|
|
|
|
self.add(coord_label)
|
|
for angle in [PI - theta_value, PI - 0.001, -TAU + 0.002]:
|
|
self.play(
|
|
Rotate(dot, angle, about_point=ORIGIN),
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(coord_label),
|
|
FadeOut(r_label),
|
|
FadeOut(theta_label),
|
|
FadeOut(radians_word),
|
|
FadeOut(r_line),
|
|
FadeOut(arc),
|
|
FadeOut(dot),
|
|
)
|
|
|
|
self.dot = dot
|
|
self.r_line = r_line
|
|
self.arc = arc
|
|
self.theta_tracker = theta_tracker
|
|
|
|
def show_all_nn_tuples(self):
|
|
dot = self.dot
|
|
arc = self.arc
|
|
r_line = self.r_line
|
|
theta_tracker = self.theta_tracker
|
|
|
|
primes = generate_prime_list(20)
|
|
non_primes = list(range(1, 20))
|
|
for prime in primes:
|
|
non_primes.remove(prime)
|
|
|
|
pp_points = VGroup(*map(self.get_nn_point, primes))
|
|
pp_points[0][1].shift(0.3 * LEFT + SMALL_BUFF * UP)
|
|
np_points = VGroup(*map(self.get_nn_point, non_primes))
|
|
pp_points.set_color(TEAL)
|
|
np_points.set_color(WHITE)
|
|
pp_points.set_stroke(BLACK, 4, background=True)
|
|
np_points.set_stroke(BLACK, 4, background=True)
|
|
|
|
frame = self.camera_frame
|
|
self.play(
|
|
ApplyMethod(frame.scale, 2),
|
|
LaggedStartMap(
|
|
FadeInFromDown, pp_points
|
|
),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStartMap(FadeIn, np_points))
|
|
self.play(frame.scale, 0.5)
|
|
self.wait()
|
|
|
|
# Talk about 1
|
|
one = np_points[0]
|
|
dot.move_to(self.get_polar_point(1, 1))
|
|
self.add(dot)
|
|
theta_tracker.clear_updaters()
|
|
theta_tracker.set_value(1)
|
|
# r_line = Line(ORIGIN, one.dot.get_center())
|
|
# r_line.set_color(self.r_color)
|
|
# pre_arc = Line(RIGHT, UR, color=self.r_color)
|
|
# theta_tracker = ValueTracker(1)
|
|
# arc = always_redraw(lambda: self.get_arc(theta_tracker.get_value()))
|
|
|
|
one_rect = SurroundingRectangle(one)
|
|
one_r_rect = SurroundingRectangle(one.label[1])
|
|
one_theta_rect = SurroundingRectangle(one.label[3])
|
|
one_theta_rect.set_color(self.theta_color)
|
|
|
|
self.play(ShowCreation(one_rect))
|
|
self.add(r_line, np_points, pp_points, one_rect)
|
|
self.play(
|
|
ReplacementTransform(one_rect, one_r_rect),
|
|
ShowCreation(r_line)
|
|
)
|
|
self.wait()
|
|
# self.play(TransformFromCopy(r_line, pre_arc))
|
|
# self.add(pre_arc, one)
|
|
self.play(
|
|
ReplacementTransform(
|
|
Line(*r_line.get_start_and_end()), arc
|
|
),
|
|
ReplacementTransform(one_r_rect, one_theta_rect)
|
|
)
|
|
self.add(arc, one, one_theta_rect)
|
|
self.play(FadeOut(one_theta_rect))
|
|
self.wait()
|
|
|
|
# Talk about 2, 3 then 4
|
|
for n in [2, 3, 4]:
|
|
self.play(
|
|
Rotate(dot, 1, about_point=ORIGIN),
|
|
theta_tracker.set_value, n,
|
|
)
|
|
self.wait()
|
|
self.play(dot.move_to, self.get_polar_point(n, n))
|
|
self.wait()
|
|
|
|
# Zoom out and show spiral
|
|
big_anim = Succession(*3 * [Animation(Mobject())], *it.chain(*[
|
|
[
|
|
AnimationGroup(
|
|
Rotate(dot, 1, about_point=ORIGIN),
|
|
ApplyMethod(theta_tracker.set_value, n),
|
|
),
|
|
ApplyMethod(dot.move_to, self.get_polar_point(n, n))
|
|
]
|
|
for n in [5, 6, 7, 8, 9]
|
|
]))
|
|
|
|
spiral = ParametricFunction(
|
|
lambda t: self.get_polar_point(t, t),
|
|
t_min=0,
|
|
t_max=25,
|
|
stroke_width=1.5,
|
|
)
|
|
|
|
# self.add(spiral, pp_points, np_points)
|
|
|
|
self.polar_grid.generate_target()
|
|
for mob in self.polar_grid:
|
|
if not isinstance(mob[0], Integer):
|
|
mob.set_stroke(width=1)
|
|
|
|
self.play(
|
|
frame.scale, 3,
|
|
big_anim,
|
|
run_time=10,
|
|
)
|
|
self.play(
|
|
# ApplyMethod(
|
|
# frame.scale, 1.5,
|
|
# run_time=2,
|
|
# rate_func=lambda t: smooth(t, 2)
|
|
# ),
|
|
ShowCreation(
|
|
spiral,
|
|
run_time=4,
|
|
),
|
|
FadeOut(r_line),
|
|
FadeOut(arc),
|
|
FadeOut(dot),
|
|
# MoveToTarget(self.polar_grid)
|
|
)
|
|
self.wait()
|
|
|
|
#
|
|
def get_nn_point(self, n):
|
|
point = self.get_polar_point(n, n)
|
|
dot = Dot(point)
|
|
coord_label = self.get_coord_label(
|
|
n, n,
|
|
include_background_rectangle=False,
|
|
num_decimal_places=0
|
|
)
|
|
coord_label.next_to(dot, UR, buff=0)
|
|
result = VGroup(dot, coord_label)
|
|
result.dot = dot
|
|
result.label = coord_label
|
|
return result
|
|
|
|
def get_polar_grid(self, radius=25):
|
|
plane = self.plane
|
|
axes = VGroup(
|
|
Line(radius * DOWN, radius * UP),
|
|
Line(radius * LEFT, radius * RIGHT),
|
|
)
|
|
axes.set_stroke(width=2)
|
|
circles = VGroup(*[
|
|
Circle(color=BLUE, stroke_width=1, radius=r)
|
|
for r in range(1, int(radius))
|
|
])
|
|
rays = VGroup(*[
|
|
Line(
|
|
ORIGIN, radius * RIGHT,
|
|
color=BLUE,
|
|
stroke_width=1,
|
|
).rotate(angle, about_point=ORIGIN)
|
|
for angle in np.arange(0, TAU, TAU / 16)
|
|
])
|
|
labels = VGroup(*[
|
|
Integer(n).scale(0.5).next_to(
|
|
plane.c2p(n, 0), DR, SMALL_BUFF
|
|
)
|
|
for n in range(1, int(radius))
|
|
])
|
|
|
|
return VGroup(
|
|
circles, rays, labels, axes,
|
|
)
|
|
|
|
def get_coord_label(self,
|
|
x=0,
|
|
y=0,
|
|
x_color=WHITE,
|
|
y_color=WHITE,
|
|
include_background_rectangle=True,
|
|
**decimal_kwargs):
|
|
coords = VGroup()
|
|
for n in x, y:
|
|
if isinstance(n, numbers.Number):
|
|
coord = DecimalNumber(n, **decimal_kwargs)
|
|
elif isinstance(n, str):
|
|
coord = TexMobject(n)
|
|
else:
|
|
raise Exception("Invalid type")
|
|
coords.add(coord)
|
|
|
|
x_coord, y_coord = coords
|
|
x_coord.set_color(x_color)
|
|
y_coord.set_color(y_color)
|
|
|
|
coord_label = VGroup(
|
|
TexMobject("("), x_coord,
|
|
TexMobject(","), y_coord,
|
|
TexMobject(")")
|
|
)
|
|
coord_label.arrange(RIGHT, buff=SMALL_BUFF)
|
|
coord_label[2].align_to(coord_label[0], DOWN)
|
|
|
|
coord_label.x_coord = x_coord
|
|
coord_label.y_coord = y_coord
|
|
if include_background_rectangle:
|
|
coord_label.add_background_rectangle()
|
|
return coord_label
|
|
|
|
def get_polar_point(self, r, theta):
|
|
plane = self.plane
|
|
return plane.c2p(r * np.cos(theta), r * np.sin(theta))
|
|
|
|
def get_arc(self, theta, r=1, color=None):
|
|
if color is None:
|
|
color = self.theta_color
|
|
return ParametricFunction(
|
|
lambda t: self.get_polar_point(1 + 0.025 * t, t),
|
|
t_min=0,
|
|
t_max=theta,
|
|
dt=0.25,
|
|
color=color,
|
|
stroke_width=3,
|
|
)
|
|
# return Arc(
|
|
# angle=theta,
|
|
# radius=r,
|
|
# stroke_color=color,
|
|
# )
|
|
|
|
|
|
class IntroducePolarPlot(RefresherOnPolarCoordinates):
|
|
def construct(self):
|
|
self.plane = NumberPlane()
|
|
grid = self.get_polar_grid()
|
|
title = TextMobject("Polar coordinates")
|
|
title.scale(3)
|
|
title.set_stroke(BLACK, 10, background=True)
|
|
title.to_edge(UP)
|
|
|
|
self.add(grid, title)
|
|
self.play(
|
|
ShowCreation(grid, lag_ratio=0.1),
|
|
run_time=3,
|
|
)
|
|
|
|
|
|
class ReplacePolarCoordinatesWithPrimes(RefresherOnPolarCoordinates):
|
|
def construct(self):
|
|
coords, p_coords = [
|
|
self.get_coord_label(
|
|
*pair,
|
|
x_color=self.r_color,
|
|
y_color=self.theta_color,
|
|
).scale(2)
|
|
for pair in [("r", "\\theta"), ("p", "p")]
|
|
]
|
|
p_coords.x_coord.set_color(LIGHT_GREY)
|
|
p_coords.y_coord.set_color(LIGHT_GREY)
|
|
|
|
some_prime = TextMobject("Some prime")
|
|
some_prime.scale(1.5)
|
|
some_prime.next_to(p_coords.get_left(), DOWN, buff=1.5)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
some_prime.get_top(), coord.get_bottom(),
|
|
stroke_width=5,
|
|
tip_length=0.4
|
|
)
|
|
for coord in [p_coords.x_coord, p_coords.y_coord]
|
|
])
|
|
|
|
equals = TexMobject("=")
|
|
equals.next_to(p_coords, LEFT)
|
|
|
|
self.add(coords)
|
|
self.wait()
|
|
self.play(
|
|
coords.next_to, equals, LEFT,
|
|
FadeIn(equals),
|
|
FadeIn(p_coords),
|
|
)
|
|
self.play(
|
|
FadeInFromDown(some_prime),
|
|
ShowCreation(arrows),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class IntroducePrimePatterns(SpiralScene):
|
|
CONFIG = {
|
|
"small_n_primes": 25000,
|
|
"big_n_primes": 1000000,
|
|
"axes_config": {
|
|
"x_min": -25,
|
|
"x_max": 25,
|
|
"y_min": -25,
|
|
"y_max": 25,
|
|
},
|
|
"spiral_scale": 3e3,
|
|
"ray_scale": 1e5,
|
|
}
|
|
|
|
def construct(self):
|
|
self.slowly_zoom_out()
|
|
self.show_clumps_of_four()
|
|
|
|
def slowly_zoom_out(self):
|
|
zoom_time = 8
|
|
|
|
prime_spiral = self.get_prime_p_spiral(self.small_n_primes)
|
|
prime_spiral.set_stroke_width(25)
|
|
self.add(prime_spiral)
|
|
|
|
self.set_scale(3, spiral=prime_spiral)
|
|
self.wait()
|
|
self.set_scale(
|
|
self.spiral_scale,
|
|
spiral=prime_spiral,
|
|
target_p_spiral_width=8,
|
|
run_time=zoom_time,
|
|
)
|
|
self.wait()
|
|
|
|
self.remove(prime_spiral)
|
|
prime_spiral = self.get_prime_p_spiral(self.big_n_primes)
|
|
prime_spiral.set_stroke_width(8)
|
|
self.set_scale(
|
|
self.ray_scale,
|
|
spiral=prime_spiral,
|
|
target_p_spiral_width=4,
|
|
run_time=zoom_time,
|
|
)
|
|
self.wait()
|
|
|
|
def show_clumps_of_four(self):
|
|
line_groups = VGroup()
|
|
for n in range(71):
|
|
group = VGroup()
|
|
for k in [-3, -1, 1, 3]:
|
|
r = ((10 * n + k) * INV_113_MOD_710) % 710
|
|
group.add(self.get_arithmetic_sequence_line(
|
|
710, r, self.big_n_primes
|
|
))
|
|
line_groups.add(group)
|
|
|
|
line_groups.set_stroke(YELLOW, 2, opacity=0.5)
|
|
|
|
self.play(ShowCreation(line_groups[0]))
|
|
for g1, g2 in zip(line_groups, line_groups[1:5]):
|
|
self.play(
|
|
FadeOut(g1),
|
|
ShowCreation(g2)
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(line_groups[4]),
|
|
LaggedStartMap(
|
|
VFadeInThenOut,
|
|
line_groups[4:],
|
|
lag_ratio=0.5,
|
|
run_time=5,
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
def get_arithmetic_sequence_line(self, N, r, max_val, skip_factor=5):
|
|
line = VMobject()
|
|
line.set_points_smoothly([
|
|
self.get_polar_point(x, x)
|
|
for x in range(r, max_val, skip_factor * N)
|
|
])
|
|
return line
|
|
|
|
|
|
class AskWhat(TeacherStudentsScene):
|
|
def construct(self):
|
|
screen = self.screen
|
|
self.student_says(
|
|
"I'm sorry,\\\\what?!?",
|
|
target_mode="angry",
|
|
look_at_arg=screen,
|
|
student_index=2,
|
|
added_anims=[
|
|
self.teacher.change, "happy", screen,
|
|
self.students[0].change, "confused", screen,
|
|
self.students[1].change, "confused", screen,
|
|
]
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class CountSpirals(IntroducePrimePatterns):
|
|
CONFIG = {
|
|
"count_sound": "pen_click.wav",
|
|
}
|
|
|
|
def construct(self):
|
|
prime_spiral = self.get_prime_p_spiral(self.small_n_primes)
|
|
|
|
self.add(prime_spiral)
|
|
self.set_scale(
|
|
self.spiral_scale,
|
|
spiral=prime_spiral,
|
|
run_time=0,
|
|
)
|
|
|
|
spiral_lines = self.get_all_primative_arithmetic_lines(
|
|
44, self.small_n_primes, INV_7_MOD_44,
|
|
)
|
|
spiral_lines.set_stroke(YELLOW, 2, opacity=0.5)
|
|
|
|
counts = VGroup()
|
|
for n, spiral in zip(it.count(1), spiral_lines):
|
|
count = Integer(n)
|
|
count.move_to(spiral.point_from_proportion(0.25))
|
|
counts.add(count)
|
|
|
|
run_time = 3
|
|
self.play(
|
|
ShowIncreasingSubsets(spiral_lines),
|
|
ShowSubmobjectsOneByOne(counts),
|
|
run_time=run_time,
|
|
rate_func=linear,
|
|
)
|
|
self.add_count_clicks(len(spiral_lines), run_time)
|
|
self.play(
|
|
counts[-1].scale, 3,
|
|
counts[-1].set_stroke, BLACK, 5, {"background": True},
|
|
)
|
|
self.wait()
|
|
|
|
def get_all_primative_arithmetic_lines(self, N, max_val, mult_factor):
|
|
lines = VGroup()
|
|
for r in range(1, N):
|
|
if get_gcd(N, r) == 1:
|
|
lines.add(
|
|
self.get_arithmetic_sequence_line(N, (mult_factor * r) % N, max_val)
|
|
)
|
|
return lines
|
|
|
|
def add_count_clicks(self, N, time, rate_func=linear):
|
|
alphas = np.arange(0, 1, 1 / N)
|
|
if rate_func is linear:
|
|
delays = time * alphas
|
|
else:
|
|
delays = time * np.array([
|
|
binary_search(rate_func, alpha, 0, 1)
|
|
for alpha in alphas
|
|
])
|
|
|
|
for delay in delays:
|
|
self.add_sound(
|
|
self.count_sound,
|
|
time_offset=-delay,
|
|
gain=-15,
|
|
)
|
|
|
|
|
|
class CountRays(CountSpirals):
|
|
def construct(self):
|
|
prime_spiral = self.get_prime_p_spiral(self.big_n_primes)
|
|
|
|
self.add(prime_spiral)
|
|
self.set_scale(
|
|
self.ray_scale,
|
|
spiral=prime_spiral,
|
|
run_time=0,
|
|
)
|
|
|
|
spiral_lines = self.get_all_primative_arithmetic_lines(
|
|
710, self.big_n_primes, INV_113_MOD_710,
|
|
)
|
|
spiral_lines.set_stroke(YELLOW, 2, opacity=0.5)
|
|
|
|
counts = VGroup()
|
|
for n, spiral in zip(it.count(1), spiral_lines):
|
|
count = Integer(n)
|
|
count.move_to(spiral.point_from_proportion(0.25))
|
|
counts.add(count)
|
|
|
|
run_time = 6
|
|
self.play(
|
|
ShowIncreasingSubsets(spiral_lines),
|
|
ShowSubmobjectsOneByOne(counts),
|
|
run_time=run_time,
|
|
rate_func=smooth,
|
|
)
|
|
self.add_count_clicks(len(spiral_lines), run_time, rate_func=smooth)
|
|
self.play(
|
|
counts[-1].scale, 3,
|
|
counts[-1].set_stroke, BLACK, 5, {"background": True},
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(spiral_lines))
|
|
self.wait()
|
|
|
|
|
|
class AskAboutRelationToPrimes(TeacherStudentsScene):
|
|
def construct(self):
|
|
numbers = TextMobject("20, 280")
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
primes = TextMobject("2, 3, 5, 7, 11, \\dots")
|
|
q_marks = TextMobject("???")
|
|
q_marks.set_color(YELLOW)
|
|
|
|
group = VGroup(primes, arrow, numbers)
|
|
group.arrange(RIGHT)
|
|
q_marks.next_to(arrow, UP)
|
|
group.add(q_marks)
|
|
group.scale(1.5)
|
|
group.next_to(self.pi_creatures, UP, LARGE_BUFF)
|
|
|
|
self.play(
|
|
self.get_student_changes(
|
|
*3 * ["maybe"],
|
|
look_at_arg=numbers,
|
|
),
|
|
self.teacher.change, "maybe", numbers,
|
|
ShowCreation(arrow),
|
|
FadeInFrom(numbers, RIGHT)
|
|
)
|
|
self.play(
|
|
FadeInFrom(primes, LEFT),
|
|
)
|
|
self.play(
|
|
LaggedStartMap(FadeInFromDown, q_marks[0]),
|
|
Blink(self.teacher)
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class ZoomOutOnPrimesWithNumbers(IntroducePrimePatterns):
|
|
CONFIG = {
|
|
"n_labeled_primes": 1000,
|
|
"big_n_primes": int(5e6),
|
|
"thicknesses": [8, 3, 2],
|
|
"thicker_target": False,
|
|
}
|
|
|
|
def construct(self):
|
|
zoom_time = 20
|
|
|
|
prime_spiral = self.get_prime_p_spiral(self.big_n_primes)
|
|
prime_spiral.set_stroke_width(25)
|
|
|
|
prime_labels = self.get_prime_labels(self.n_labeled_primes)
|
|
|
|
self.add(prime_spiral)
|
|
self.add(prime_labels)
|
|
|
|
scales = [self.spiral_scale, self.ray_scale, 5e5]
|
|
thicknesses = self.thicknesses
|
|
|
|
for scale, tp in zip(scales, thicknesses):
|
|
kwargs = {
|
|
"spiral": prime_spiral,
|
|
"to_shrink": prime_labels,
|
|
"run_time": zoom_time,
|
|
"target_p_spiral_width": tp,
|
|
}
|
|
if self.thicker_target:
|
|
kwargs["target_p_spiral_width"] += 1
|
|
self.set_scale(scale, **kwargs)
|
|
prime_spiral.set_stroke_width(tp)
|
|
self.wait()
|
|
self.remove(prime_labels)
|
|
|
|
|
|
class ThickZoomOutOnPrimesWithNumbers(ZoomOutOnPrimesWithNumbers):
|
|
CONFIG = {
|
|
# The only purpose of this scene is for overlay
|
|
# with the last one to smooth things out.
|
|
"thicker_target": True,
|
|
}
|
|
|
|
|
|
class HighlightGapsInSpirals(IntroducePrimePatterns):
|
|
def construct(self):
|
|
self.setup_spiral()
|
|
|
|
max_n_tracker = ValueTracker(0)
|
|
get_max_n = max_n_tracker.get_value
|
|
gaps = always_redraw(lambda: VGroup(*[
|
|
self.get_highlighted_gap(n - 1, n + 1, get_max_n())
|
|
for n in [11, 33]
|
|
]))
|
|
|
|
self.add(gaps)
|
|
self.play(max_n_tracker.set_value, 25000, run_time=5)
|
|
gaps.clear_updaters()
|
|
self.play(FadeOut(gaps))
|
|
|
|
def setup_spiral(self):
|
|
p_spiral = self.get_p_spiral(read_in_primes(self.small_n_primes))
|
|
self.add(p_spiral)
|
|
self.set_scale(
|
|
scale=self.spiral_scale,
|
|
spiral=p_spiral,
|
|
target_p_spiral_width=8,
|
|
run_time=0,
|
|
)
|
|
|
|
def get_highlighted_gap(self, n1, n2, max_n):
|
|
l1, l2 = [
|
|
[
|
|
self.get_polar_point(k, k)
|
|
for k in range(INV_7_MOD_44 * n, int(max_n), 5 * 44)
|
|
]
|
|
for n in (n1, n2)
|
|
]
|
|
|
|
if len(l1) == 0 or len(l2) == 0:
|
|
return VectorizedPoint()
|
|
|
|
result = VMobject()
|
|
result.set_points_as_corners(
|
|
[*l1, *reversed(l2)]
|
|
)
|
|
result.make_smooth()
|
|
|
|
result.set_stroke(GREY, width=0)
|
|
result.set_fill(DARK_GREY, 1)
|
|
|
|
return result
|
|
|
|
|
|
class QuestionIsMisleading(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Whoa, is this some\\\\divine hidden structure\\\\in the primes?",
|
|
target_mode="surprised",
|
|
student_index=0,
|
|
added_anims=[
|
|
self.students[1].change, "pondering",
|
|
self.students[2].change, "pondering",
|
|
]
|
|
)
|
|
self.wait(2)
|
|
|
|
self.students[0].bubble = None
|
|
self.teacher_says(
|
|
"Er...not exactly",
|
|
bubble_kwargs={"width": 3, "height": 2},
|
|
target_mode="guilty"
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class JustPrimesLabel(Scene):
|
|
def construct(self):
|
|
text = TextMobject("Just the primes")
|
|
text.scale(2)
|
|
text.to_corner(UL)
|
|
self.play(Write(text))
|
|
self.wait(3)
|
|
self.play(FadeOutAndShift(text, DOWN))
|
|
|
|
|
|
class DirichletComingUp(Scene):
|
|
def construct(self):
|
|
image = ImageMobject("Dirichlet")
|
|
image.set_height(3)
|
|
words = TextMobject(
|
|
"Coming up: \\\\", "Dirichlet's theorem",
|
|
alignment="",
|
|
)
|
|
words.set_color_by_tex("Dirichlet's", YELLOW)
|
|
words.scale(1.5)
|
|
words.next_to(image, RIGHT)
|
|
Group(words, image).center()
|
|
|
|
self.play(
|
|
FadeInFrom(image, RIGHT),
|
|
FadeInFrom(words, LEFT),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class ImagineYouFoundIt(TeacherStudentsScene):
|
|
def construct(self):
|
|
you = self.students[1]
|
|
others = VGroup(
|
|
self.students[0],
|
|
self.students[2],
|
|
self.teacher,
|
|
)
|
|
bubble = you.get_bubble(direction=LEFT)
|
|
bubble[-1].set_fill(GREEN_SCREEN, 1)
|
|
|
|
you_label = TextMobject("You")
|
|
arrow = Vector(DOWN)
|
|
arrow.next_to(you, UP)
|
|
you_label.next_to(arrow, UP)
|
|
|
|
self.play(
|
|
you.change, "hesitant", you_label,
|
|
FadeInFromDown(you_label),
|
|
GrowArrow(arrow),
|
|
others.set_opacity, 0.25,
|
|
)
|
|
self.play(Blink(you))
|
|
self.play(
|
|
FadeIn(bubble),
|
|
FadeOut(you_label),
|
|
FadeOut(arrow),
|
|
you.change, "pondering",
|
|
)
|
|
self.play(you.look_at, bubble.get_corner(UR))
|
|
self.play(Blink(you))
|
|
self.wait()
|
|
self.play(you.change, "hooray")
|
|
self.play(Blink(you))
|
|
self.wait()
|
|
self.play(you.change, "sassy", bubble.get_top())
|
|
self.wait(6)
|
|
|
|
|
|
class ShowSpiralsForWholeNumbers(CountSpirals):
|
|
CONFIG = {
|
|
"max_prime": 10000,
|
|
"scale_44": 1e3,
|
|
"scale_6": 10,
|
|
"n_labels": 100,
|
|
"axes_config": {
|
|
"x_min": -50,
|
|
"x_max": 50,
|
|
"y_min": -50,
|
|
"y_max": 50,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.zoom_out_with_whole_numbers()
|
|
self.count_44_spirals()
|
|
self.zoom_back_in_to_6()
|
|
|
|
def zoom_out_with_whole_numbers(self):
|
|
wholes = self.get_p_spiral(range(self.max_prime))
|
|
primes = self.get_prime_p_spiral(self.max_prime)
|
|
|
|
wholes.set_color(YELLOW)
|
|
wholes.set_stroke_width(20)
|
|
primes.set_stroke_width(20)
|
|
spiral = PGroup(wholes, primes)
|
|
|
|
labels = self.get_labels(range(1, self.n_labels))
|
|
|
|
self.add(spiral, labels)
|
|
self.set_scale(
|
|
self.scale_44,
|
|
spiral=spiral,
|
|
to_shrink=labels,
|
|
target_p_spiral_width=6,
|
|
run_time=10,
|
|
)
|
|
self.wait(2)
|
|
|
|
self.spiral = spiral
|
|
self.labels = labels
|
|
|
|
def count_44_spirals(self):
|
|
curr_spiral = self.spiral
|
|
|
|
new_spirals = PGroup(*[
|
|
self.get_p_spiral(range(
|
|
(INV_7_MOD_44 * k) % 44, self.max_prime, 44
|
|
))
|
|
for k in range(44)
|
|
])
|
|
new_spirals.set_color(YELLOW)
|
|
|
|
counts = VGroup()
|
|
for n, spiral in zip(it.count(1), new_spirals):
|
|
count = Integer(n)
|
|
count.scale(2)
|
|
count.move_to(spiral.points[50])
|
|
counts.add(count)
|
|
|
|
self.remove(curr_spiral)
|
|
run_time = 3
|
|
self.play(
|
|
ShowIncreasingSubsets(new_spirals),
|
|
ShowSubmobjectsOneByOne(counts),
|
|
run_time=run_time,
|
|
rate_func=linear,
|
|
)
|
|
self.add_count_clicks(44, run_time)
|
|
self.play(
|
|
counts[-1].scale, 2, {"about_edge": DL},
|
|
counts[-1].set_stroke, BLACK, 5, {"background": True},
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(counts[-1]),
|
|
FadeOut(new_spirals),
|
|
FadeIn(curr_spiral),
|
|
)
|
|
|
|
def zoom_back_in_to_6(self):
|
|
spiral = self.spiral
|
|
|
|
self.rescale_labels(self.labels)
|
|
self.set_scale(
|
|
self.scale_6,
|
|
spiral=spiral,
|
|
to_shrink=self.labels,
|
|
target_p_spiral_width=15,
|
|
run_time=6,
|
|
)
|
|
self.wait()
|
|
|
|
def rescale_labels(self, labels):
|
|
for i, label in zip(it.count(1), labels):
|
|
height = label.get_height()
|
|
label.set_height(
|
|
3 * height / (i**0.25),
|
|
about_point=label.get_bottom() + 0.5 * label.get_height() * DOWN,
|
|
)
|
|
|
|
|
|
class PrimeSpiralsAtScale1000(SpiralScene):
|
|
def construct(self):
|
|
spiral = self.get_prime_p_spiral(10000)
|
|
self.add(spiral)
|
|
self.set_scale(
|
|
scale=1000,
|
|
spiral=spiral,
|
|
target_p_spiral_width=15,
|
|
run_time=0,
|
|
)
|
|
|
|
|
|
class SeparateIntoTwoQuestions(Scene):
|
|
def construct(self):
|
|
top_q = TextMobject("Why do", " primes", " cause", " spirals", "?")
|
|
top_q.scale(2)
|
|
top_q.to_edge(UP)
|
|
|
|
q1 = TextMobject("Where do the\\\\", "spirals", " come from?")
|
|
q2 = TextMobject("What happens when\\\\", "filtering to", " primes", "?")
|
|
for q in q1, q2:
|
|
q.scale(1.3)
|
|
q.next_to(top_q, DOWN, LARGE_BUFF)
|
|
q1.to_edge(LEFT)
|
|
q1.set_color(YELLOW)
|
|
q2.to_edge(RIGHT)
|
|
q2.set_color(TEAL)
|
|
|
|
v_line = DashedLine(
|
|
top_q.get_bottom() + MED_SMALL_BUFF * DOWN,
|
|
FRAME_HEIGHT * DOWN / 2,
|
|
)
|
|
|
|
self.add(top_q)
|
|
self.wait()
|
|
|
|
for q, text in [(q1, "spirals"), (q2, "primes")]:
|
|
self.play(
|
|
top_q.get_part_by_tex(text).set_color, q.get_color(),
|
|
TransformFromCopy(
|
|
top_q.get_part_by_tex(text),
|
|
q.get_part_by_tex(text),
|
|
),
|
|
LaggedStartMap(
|
|
FadeIn,
|
|
filter(
|
|
lambda m: m is not q.get_part_by_tex(text),
|
|
q,
|
|
)
|
|
),
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(v_line))
|
|
self.wait()
|
|
|
|
|
|
class ExplainSixSpirals(ShowSpiralsForWholeNumbers):
|
|
CONFIG = {
|
|
"max_N": 150,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_spirals_and_labels()
|
|
self.comment_on_arms()
|
|
self.talk_though_multiples_of_six()
|
|
self.limit_to_primes()
|
|
|
|
def add_spirals_and_labels(self):
|
|
max_N = self.max_N
|
|
|
|
spiral = self.get_v_spiral(range(max_N))
|
|
primes = generate_prime_list(max_N)
|
|
spiral.set_color(YELLOW)
|
|
for n, box in enumerate(spiral):
|
|
if n in primes:
|
|
box.set_color(TEAL)
|
|
|
|
labels = self.get_labels(range(max_N))
|
|
|
|
self.add(spiral, labels)
|
|
self.set_scale(
|
|
spiral=spiral,
|
|
scale=self.scale_6,
|
|
to_shrink=labels,
|
|
min_box_width=0.08,
|
|
run_time=0,
|
|
)
|
|
self.rescale_labels(labels)
|
|
|
|
self.spiral = spiral
|
|
self.labels = labels
|
|
|
|
def comment_on_arms(self):
|
|
labels = self.labels
|
|
spiral = self.spiral
|
|
|
|
label_groups = VGroup(*[labels[k::6] for k in range(6)])
|
|
spiral_groups = VGroup(*[spiral[k::6] for k in range(6)])
|
|
six_groups = VGroup(*[
|
|
VGroup(sg, lg)
|
|
for sg, lg in zip(spiral_groups, label_groups)
|
|
])
|
|
rect_groups = VGroup(*[
|
|
VGroup(*[
|
|
SurroundingRectangle(label, stroke_width=2, buff=0.05)
|
|
for label in group
|
|
])
|
|
for group in label_groups
|
|
])
|
|
|
|
formula = VGroup(
|
|
*TexMobject("6k", "+"),
|
|
Integer(1)
|
|
)
|
|
formula.arrange(RIGHT, buff=SMALL_BUFF)
|
|
formula.scale(2)
|
|
formula.set_color(YELLOW)
|
|
formula.to_corner(UL)
|
|
formula_rect = SurroundingRectangle(formula, buff=MED_LARGE_BUFF - SMALL_BUFF)
|
|
formula_rect.set_fill(DARK_GREY, opacity=1)
|
|
formula_rect.set_stroke(WHITE, 1)
|
|
|
|
# 6k
|
|
self.add(six_groups, formula_rect)
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, rect_groups[0]),
|
|
FadeInFromDown(formula_rect),
|
|
FadeInFromDown(formula[0]),
|
|
*[
|
|
ApplyMethod(group.set_opacity, 0.25)
|
|
for group in six_groups[1:]
|
|
],
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
LaggedStartMap(
|
|
FadeOut, rect_groups[0],
|
|
run_time=1,
|
|
),
|
|
)
|
|
self.wait()
|
|
|
|
# 6k + 1
|
|
self.play(
|
|
six_groups[0].set_opacity, 0.25,
|
|
six_groups[1].set_opacity, 1,
|
|
FadeIn(formula[1:]),
|
|
)
|
|
self.wait(2)
|
|
|
|
# 6k + m
|
|
for m in [2, 3, 4, 5]:
|
|
self.play(
|
|
six_groups[m - 1].set_opacity, 0.25,
|
|
six_groups[m].set_opacity, 1,
|
|
ChangeDecimalToValue(formula[2], m),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
six_groups[5].set_opacity, 0.25,
|
|
six_groups[0].set_opacity, 1,
|
|
formula[1:].set_opacity, 0,
|
|
)
|
|
self.wait()
|
|
|
|
self.six_groups = six_groups
|
|
self.formula = VGroup(formula_rect, *formula)
|
|
|
|
def talk_though_multiples_of_six(self):
|
|
spiral = self.spiral
|
|
labels = self.labels
|
|
formula = self.formula
|
|
|
|
# Zoom in
|
|
self.add(spiral, labels, formula)
|
|
self.set_scale(
|
|
4.5,
|
|
spiral=spiral,
|
|
to_shrink=labels,
|
|
run_time=2,
|
|
)
|
|
self.wait()
|
|
|
|
boxes = VGroup(*[
|
|
VGroup(b.copy(), l.copy())
|
|
for b, l in zip(spiral, labels)
|
|
])
|
|
boxes.set_opacity(1)
|
|
|
|
lines = VGroup(*[
|
|
Line(ORIGIN, box[0].get_center())
|
|
for box in boxes
|
|
])
|
|
lines.set_stroke(LIGHT_GREY, width=2)
|
|
|
|
arcs = self.get_arcs(range(31))
|
|
|
|
trash = VGroup()
|
|
|
|
def show_steps(start, stop, added_anims=None, run_time=2):
|
|
if added_anims is None:
|
|
added_anims = []
|
|
self.play(
|
|
*[
|
|
ShowSubmobjectsOneByOne(group[start:stop + 1])
|
|
for group in [arcs, boxes, lines]
|
|
],
|
|
*added_anims,
|
|
rate_func=linear,
|
|
run_time=run_time,
|
|
)
|
|
self.add_count_clicks(N=6, time=run_time)
|
|
trash.add(VGroup(arcs[stop], boxes[stop], lines[stop]))
|
|
|
|
# Writing next to the 6
|
|
six = boxes[6][1]
|
|
rhs = TexMobject(
|
|
"\\text{radians}",
|
|
"\\approx",
|
|
"2\\pi",
|
|
"\\text{ radians}"
|
|
)
|
|
rhs.next_to(six, RIGHT, 2 * SMALL_BUFF, aligned_edge=DOWN)
|
|
rhs.add_background_rectangle()
|
|
tau_value = TexMobject("{:.8}\\dots".format(TAU))
|
|
tau_value.next_to(rhs[3], UP, aligned_edge=LEFT)
|
|
|
|
# Animations
|
|
show_steps(0, 6, run_time=3)
|
|
self.wait()
|
|
self.play(FadeIn(rhs))
|
|
self.wait()
|
|
self.play(FadeInFromDown(tau_value))
|
|
self.wait(2)
|
|
|
|
show_steps(6, 12)
|
|
self.wait()
|
|
|
|
show_steps(12, 18)
|
|
self.wait()
|
|
|
|
# Zoom out
|
|
frame = self.camera_frame
|
|
frame.add(formula)
|
|
show_steps(18, 24, added_anims=[frame.scale, 2.5])
|
|
self.wait()
|
|
show_steps(24, 30)
|
|
self.wait(2)
|
|
|
|
self.play(
|
|
FadeOut(trash),
|
|
FadeOut(rhs),
|
|
FadeOut(tau_value),
|
|
spiral.set_opacity, 1,
|
|
labels.set_opacity, 1,
|
|
formula[1].set_opacity, 0,
|
|
)
|
|
|
|
def limit_to_primes(self):
|
|
formula = self.formula
|
|
formula_rect, six_k, plus, m_sym = formula
|
|
spiral = self.spiral
|
|
labels = self.labels
|
|
six_groups = self.six_groups
|
|
frame = self.camera_frame
|
|
|
|
boxes = VGroup(*[
|
|
VGroup(b, l)
|
|
for b, l in zip(spiral, labels)
|
|
])
|
|
prime_numbers = read_in_primes(self.max_N)
|
|
primes = VGroup()
|
|
non_primes = VGroup()
|
|
for n, box in enumerate(boxes):
|
|
if n in prime_numbers:
|
|
primes.add(box)
|
|
else:
|
|
non_primes.add(box)
|
|
|
|
prime_label = TextMobject("Primes")
|
|
prime_label.set_color(TEAL)
|
|
prime_label.match_width(VGroup(six_k, m_sym))
|
|
prime_label.move_to(six_k, LEFT)
|
|
|
|
# Show just primes
|
|
self.add(primes, non_primes, formula)
|
|
self.play(
|
|
FadeIn(prime_label),
|
|
non_primes.set_opacity, 0.25,
|
|
)
|
|
frame.add(prime_label)
|
|
self.play(
|
|
frame.scale, 1.5,
|
|
run_time=2,
|
|
)
|
|
self.wait(2)
|
|
|
|
cross_groups = VGroup()
|
|
for group in six_groups:
|
|
group.save_state()
|
|
boxes, labels = group
|
|
cross_group = VGroup()
|
|
for label in labels:
|
|
cross_group.add(Cross(label))
|
|
cross_groups.add(cross_group)
|
|
cross_groups.set_stroke(width=3)
|
|
cross_groups[2].remove(cross_groups[2][0])
|
|
cross_groups[3].remove(cross_groups[3][0])
|
|
|
|
# Show multiples of 6
|
|
for r in [0, 2, 4, 3]:
|
|
arm = six_groups[r]
|
|
crosses = cross_groups[r]
|
|
self.add(arm, frame)
|
|
|
|
anims = [arm.set_opacity, 1]
|
|
if r == 0:
|
|
anims += [
|
|
prime_label.set_opacity, 0,
|
|
six_k.set_opacity, 1
|
|
]
|
|
elif r == 2:
|
|
m_sym.set_value(2)
|
|
anims += [
|
|
plus.set_opacity, 1,
|
|
m_sym.set_opacity, 1,
|
|
]
|
|
else:
|
|
anims.append(ChangeDecimalToValue(m_sym, r))
|
|
self.play(*anims)
|
|
self.add(*crosses, frame)
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, crosses),
|
|
)
|
|
self.wait()
|
|
|
|
# Fade forbidden groups
|
|
to_fade = VGroup(*[
|
|
VGroup(six_groups[r], cross_groups[r])
|
|
for r in (0, 2, 3, 4)
|
|
])
|
|
self.add(to_fade, frame)
|
|
self.play(
|
|
to_fade.set_opacity, 0.25,
|
|
VGroup(six_k, plus, m_sym).set_opacity, 0,
|
|
prime_label.set_opacity, 1,
|
|
)
|
|
self.wait()
|
|
|
|
#
|
|
def arc_func(self, t):
|
|
r = 0.25 + 0.02 * t
|
|
return r * np.array([np.cos(t), np.sin(t), 0])
|
|
|
|
def get_arc(self, n):
|
|
if n == 0:
|
|
return VectorizedPoint()
|
|
return ParametricFunction(
|
|
self.arc_func,
|
|
t_min=0,
|
|
t_max=n,
|
|
step_size=0.1,
|
|
stroke_width=2,
|
|
stroke_color=PINK,
|
|
)
|
|
|
|
def get_arcs(self, sequence):
|
|
return VGroup(*map(self.get_arc, sequence))
|
|
|
|
|
|
class IntroduceResidueClassTerminology(Scene):
|
|
def construct(self):
|
|
self.add_title()
|
|
self.add_sequences()
|
|
self.add_terms()
|
|
self.highlight_example()
|
|
self.simple_english()
|
|
|
|
def add_title(self):
|
|
title = TextMobject("Overly-fancy ", "terminology")
|
|
title.scale(1.5)
|
|
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
underline = Line().match_width(title)
|
|
underline.next_to(title, DOWN, SMALL_BUFF)
|
|
|
|
pre_title = TextMobject("Terminology")
|
|
pre_title.replace(title, dim_to_match=1)
|
|
|
|
self.play(FadeInFromDown(pre_title))
|
|
self.wait()
|
|
title[0].set_color(BLUE)
|
|
underline.set_color(BLUE)
|
|
self.play(
|
|
ReplacementTransform(pre_title[0], title[1]),
|
|
FadeInFrom(title[0], RIGHT),
|
|
GrowFromCenter(underline)
|
|
)
|
|
self.play(
|
|
title[0].set_color, WHITE,
|
|
underline.set_color, WHITE,
|
|
)
|
|
self.wait()
|
|
|
|
title.add(underline)
|
|
self.add(title)
|
|
|
|
self.title = title
|
|
self.underline = underline
|
|
|
|
def add_sequences(self):
|
|
sequences = VGroup()
|
|
n_terms = 7
|
|
|
|
for r in range(6):
|
|
sequence = VGroup(*[
|
|
Integer(6 * k + r)
|
|
for k in range(n_terms)
|
|
])
|
|
sequence.arrange(RIGHT, buff=0.4)
|
|
sequences.add(sequence)
|
|
|
|
sequences.arrange(DOWN, buff=0.7, aligned_edge=LEFT)
|
|
for sequence in sequences:
|
|
for s1, s2 in zip(sequence[:n_terms], sequences[-1]):
|
|
s1.align_to(s2, RIGHT)
|
|
commas = VGroup()
|
|
for num in sequence[:-1]:
|
|
comma = TextMobject(",")
|
|
comma.next_to(num.get_corner(DR), RIGHT, SMALL_BUFF)
|
|
commas.add(comma)
|
|
dots = TexMobject("\\dots")
|
|
dots.next_to(sequence.get_corner(DR), RIGHT, SMALL_BUFF)
|
|
sequence.numbers = VGroup(*sequence)
|
|
sequence.commas = commas
|
|
sequence.dots = dots
|
|
|
|
sequence.add(*commas)
|
|
sequence.add(dots)
|
|
sequence.sort(lambda p: p[0])
|
|
|
|
labels = VGroup(*[
|
|
TexMobject("6k + {}:".format(r))
|
|
for r in range(6)
|
|
])
|
|
labels.set_color(YELLOW)
|
|
for label, sequence in zip(labels, sequences):
|
|
label.next_to(sequence, LEFT, MED_LARGE_BUFF)
|
|
|
|
group = VGroup(sequences, labels)
|
|
group.to_edge(LEFT).to_edge(DOWN, buff=MED_LARGE_BUFF)
|
|
|
|
self.add(labels)
|
|
self.play(LaggedStart(*[
|
|
LaggedStartMap(
|
|
FadeInFrom, sequence,
|
|
lambda m: (m, LEFT),
|
|
)
|
|
for sequence in sequences
|
|
], lag_ratio=0.3))
|
|
self.wait()
|
|
|
|
self.sequences = sequences
|
|
self.sequence_labels = labels
|
|
|
|
def add_terms(self):
|
|
sequences = self.sequences
|
|
|
|
terms = TextMobject(
|
|
"``", "Residue\\\\",
|
|
"classes\\\\",
|
|
"mod ", "6''"
|
|
)
|
|
terms.scale(1.5)
|
|
terms.set_color(YELLOW)
|
|
terms.to_edge(RIGHT)
|
|
|
|
res_brace = Brace(terms.get_part_by_tex("Residue"), UP)
|
|
remainder = TextMobject("Remainder")
|
|
remainder.next_to(res_brace, UP, SMALL_BUFF)
|
|
|
|
mod_brace = Brace(terms.get_part_by_tex("mod"), DOWN)
|
|
mod_def = TextMobject(
|
|
"``where the thing\\\\you divide by is''"
|
|
)
|
|
mod_def.next_to(mod_brace, DOWN, SMALL_BUFF)
|
|
|
|
arrows = VGroup(*[
|
|
Arrow(terms.get_left(), sequence.get_right())
|
|
for sequence in sequences
|
|
])
|
|
arrows.set_color(YELLOW)
|
|
|
|
self.play(
|
|
FadeIn(terms),
|
|
LaggedStartMap(ShowCreation, arrows),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(res_brace),
|
|
FadeInFromDown(remainder),
|
|
)
|
|
self.wait()
|
|
self.play(GrowFromCenter(mod_brace))
|
|
self.play(Write(mod_def))
|
|
self.wait()
|
|
|
|
self.terminology = VGroup(
|
|
terms,
|
|
res_brace, remainder,
|
|
mod_brace, mod_def,
|
|
arrows
|
|
)
|
|
|
|
def highlight_example(self):
|
|
sequences = self.sequences
|
|
labels = self.sequence_labels
|
|
|
|
r = 2
|
|
k = 2
|
|
sequence = sequences[r]
|
|
label = labels[r]
|
|
r_tex = label[0][3]
|
|
n_rects = VGroup(*[
|
|
SurroundingRectangle(num)
|
|
for num in sequence.numbers
|
|
])
|
|
r_rect = SurroundingRectangle(r_tex)
|
|
n_rects.set_color(RED)
|
|
r_rect.set_color(RED)
|
|
|
|
n_rect = n_rects.submobjects.pop(k)
|
|
|
|
self.play(ShowCreation(n_rect))
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(n_rect, r_rect, path_arc=30 * DEGREES)
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(n_rects))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreationThenFadeOut(
|
|
self.underline.copy().set_color(PINK),
|
|
)
|
|
)
|
|
|
|
def simple_english(self):
|
|
terminology = self.terminology
|
|
sequences = self.sequences
|
|
|
|
randy = Randolph()
|
|
randy.set_height(2)
|
|
randy.flip()
|
|
randy.to_corner(DR)
|
|
|
|
new_phrase = TextMobject("Everything 2 above\\\\a multiple of 6")
|
|
new_phrase.scale(1.2)
|
|
new_phrase.set_color(RED_B)
|
|
new_phrase.next_to(sequences[2])
|
|
|
|
self.play(
|
|
FadeOut(terminology),
|
|
FadeIn(new_phrase),
|
|
VFadeIn(randy),
|
|
randy.change, "sassy"
|
|
)
|
|
self.wait()
|
|
self.play(randy.change, "angry")
|
|
for x in range(2):
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(new_phrase),
|
|
FadeIn(terminology),
|
|
FadeOutAndShift(randy, DOWN)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class SimpleLongDivision(MovingCameraScene):
|
|
CONFIG = {
|
|
"camera_config": {
|
|
"background_color": DARKER_GREY
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
divisor = Integer(6)
|
|
num = Integer(20)
|
|
quotient = Integer(num.get_value() // divisor.get_value())
|
|
to_subtract = Integer(-1 * quotient.get_value() * divisor.get_value())
|
|
remainder = Integer(num.get_value() + to_subtract.get_value())
|
|
|
|
div_sym = VMobject()
|
|
div_sym.set_points_as_corners([0.2 * UP, UP, UP + 3 * RIGHT])
|
|
|
|
divisor.next_to(div_sym, LEFT, MED_SMALL_BUFF)
|
|
num.next_to(divisor, RIGHT, MED_LARGE_BUFF)
|
|
to_subtract.next_to(num, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT)
|
|
h_line = Line(LEFT, RIGHT)
|
|
h_line.next_to(to_subtract, DOWN, buff=MED_SMALL_BUFF)
|
|
remainder.next_to(to_subtract, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT)
|
|
quotient.next_to(num, UP, buff=MED_LARGE_BUFF, aligned_edge=RIGHT)
|
|
|
|
remainder_rect = SurroundingRectangle(remainder)
|
|
remainder_rect.set_color(RED)
|
|
|
|
frame = self.camera_frame
|
|
frame.scale(0.7, about_point=ORIGIN)
|
|
|
|
divisor.set_color(YELLOW)
|
|
num.set_color(RED)
|
|
|
|
self.add(divisor)
|
|
self.add(div_sym)
|
|
self.add(num)
|
|
self.play(FadeInFromDown(quotient))
|
|
self.play(
|
|
TransformFromCopy(divisor, to_subtract.copy()),
|
|
TransformFromCopy(quotient, to_subtract),
|
|
)
|
|
self.play(ShowCreation(h_line))
|
|
self.play(Write(remainder))
|
|
self.play(ShowCreation(remainder_rect))
|
|
self.wait()
|
|
self.play(FadeOut(remainder_rect))
|
|
|
|
|
|
class ZoomOutWords(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Zoom out!")
|
|
words.scale(3)
|
|
self.play(FadeInFromLarge(words))
|
|
self.wait()
|
|
|
|
|
|
class Explain44Spirals(ExplainSixSpirals):
|
|
CONFIG = {
|
|
"max_N": 3000,
|
|
"initial_scale": 10,
|
|
"zoom_factor_1": 7,
|
|
"zoom_factor_2": 3,
|
|
"n_labels": 80,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_spirals_and_labels()
|
|
self.show_44_steps()
|
|
self.show_top_right_arithmetic()
|
|
self.show_pi_approx_arithmetic()
|
|
self.count_by_44()
|
|
|
|
def add_spirals_and_labels(self):
|
|
max_N = self.max_N
|
|
|
|
wholes = self.get_p_spiral(range(max_N))
|
|
primes = self.get_prime_p_spiral(max_N)
|
|
wholes.set_color(YELLOW)
|
|
spiral = PGroup(wholes, primes)
|
|
|
|
labels = self.get_labels(range(self.n_labels))
|
|
|
|
self.add(spiral, labels)
|
|
self.set_scale(
|
|
spiral=spiral,
|
|
scale=self.initial_scale,
|
|
to_shrink=labels,
|
|
target_p_spiral_width=10,
|
|
run_time=0,
|
|
)
|
|
self.rescale_labels(labels)
|
|
|
|
self.spiral = spiral
|
|
self.labels = labels
|
|
|
|
def show_44_steps(self):
|
|
labels = self.labels
|
|
|
|
ns = range(45)
|
|
points = [self.get_polar_point(n, n) for n in ns]
|
|
lines = VGroup(*[
|
|
Line(ORIGIN, point)
|
|
for point in points
|
|
])
|
|
lines.set_stroke(WHITE, 2)
|
|
arcs = self.get_arcs(ns)
|
|
|
|
opaque_labels = labels.copy()
|
|
labels.set_opacity(0.25)
|
|
|
|
trash = VGroup()
|
|
|
|
def show_steps(start, stop, added_anims=None, run_time=2):
|
|
if added_anims is None:
|
|
added_anims = []
|
|
|
|
def rate_func(t):
|
|
return smooth(t, 2)
|
|
|
|
self.play(
|
|
*[
|
|
ShowSubmobjectsOneByOne(group[start:stop + 1])
|
|
for group in [arcs, opaque_labels, lines]
|
|
],
|
|
*added_anims,
|
|
rate_func=rate_func,
|
|
run_time=run_time,
|
|
)
|
|
self.add_count_clicks(
|
|
N=(stop - start), time=run_time,
|
|
rate_func=rate_func
|
|
)
|
|
trash.add(arcs[stop], opaque_labels[stop], lines[stop])
|
|
|
|
show_steps(0, 6)
|
|
self.wait()
|
|
show_steps(6, 44, added_anims=[FadeOut(trash)], run_time=4)
|
|
self.wait()
|
|
|
|
self.spiral_group = trash[-3:]
|
|
|
|
def show_top_right_arithmetic(self):
|
|
labels = self.labels
|
|
ff = labels[44].copy()
|
|
ff.generate_target()
|
|
|
|
radians = TextMobject("radians")
|
|
ff.target.scale(1.5)
|
|
ff.target.set_opacity(1)
|
|
|
|
unit_conversion = TexMobject(
|
|
"/\\,", "\\left(", "2\\pi",
|
|
"{\\text{radians}", "\\over", "\\text{rotations}}",
|
|
"\\right)"
|
|
)
|
|
unit_conversion[1:].scale(0.7, about_edge=LEFT)
|
|
|
|
top_line = VGroup(ff.target, radians, unit_conversion)
|
|
top_line.arrange(RIGHT)
|
|
ff.target.align_to(radians, DOWN)
|
|
top_line.to_corner(UR, buff=0.4)
|
|
|
|
next_line = TexMobject(
|
|
"=", "44", "/", "2\\pi",
|
|
"\\text{ rotations}"
|
|
)
|
|
next_line.next_to(top_line, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT)
|
|
|
|
brace = Brace(next_line[1:4], DOWN, buff=SMALL_BUFF)
|
|
value = DecimalNumber(44 / TAU, num_decimal_places=8, show_ellipsis=True)
|
|
value.next_to(brace, DOWN)
|
|
|
|
rect = SurroundingRectangle(VGroup(top_line, value), buff=MED_SMALL_BUFF)
|
|
rect.set_stroke(WHITE, 2)
|
|
rect.set_fill(DARKER_GREY, 0.9)
|
|
|
|
self.play(MoveToTarget(ff))
|
|
top_line.add(ff)
|
|
self.play(FadeInFrom(radians, LEFT))
|
|
self.wait()
|
|
self.add(rect, top_line, unit_conversion)
|
|
self.play(
|
|
FadeIn(rect),
|
|
FadeIn(unit_conversion),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(ff, next_line.get_part_by_tex("44")),
|
|
FadeIn(next_line.get_part_by_tex("=")),
|
|
TransformFromCopy(
|
|
unit_conversion.get_part_by_tex("/"),
|
|
next_line.get_part_by_tex("/"),
|
|
),
|
|
TransformFromCopy(
|
|
unit_conversion.get_part_by_tex("rotations"),
|
|
next_line.get_part_by_tex("rotations"),
|
|
),
|
|
TransformFromCopy(
|
|
unit_conversion.get_part_by_tex("2\\pi"),
|
|
next_line.get_part_by_tex("2\\pi"),
|
|
),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(value),
|
|
)
|
|
self.wait()
|
|
|
|
self.right_arithmetic = VGroup(
|
|
rect, top_line, next_line,
|
|
brace, value
|
|
)
|
|
|
|
def show_pi_approx_arithmetic(self):
|
|
ra = self.right_arithmetic
|
|
ra_rect, ra_l1, ra_l2, ra_brace, ra_value = ra
|
|
|
|
lines = VGroup(
|
|
TexMobject("{44", "\\over", "2\\pi}", "\\approx", "7"),
|
|
TexMobject("\\Leftrightarrow"),
|
|
TexMobject("{44", "\\over", "7}", "\\approx", "2\\pi"),
|
|
TexMobject("{22", "\\over", "7}", "\\approx", "\\pi"),
|
|
)
|
|
lines.arrange(RIGHT, buff=MED_SMALL_BUFF)
|
|
lines.to_corner(UL)
|
|
lines[3].move_to(lines[2], LEFT)
|
|
|
|
rect = SurroundingRectangle(lines, buff=MED_LARGE_BUFF)
|
|
rect.match_style(ra_rect)
|
|
|
|
self.play(
|
|
FadeIn(rect),
|
|
LaggedStart(
|
|
TransformFromCopy(ra_l2[1:4], lines[0][:3]),
|
|
FadeIn(lines[0].get_part_by_tex("approx")),
|
|
TransformFromCopy(ra_value[0], lines[0].get_part_by_tex("7")),
|
|
run_time=2,
|
|
)
|
|
)
|
|
self.wait()
|
|
l0_copy = lines[0].copy()
|
|
self.play(
|
|
l0_copy.move_to, lines[2],
|
|
Write(lines[1]),
|
|
)
|
|
self.play(
|
|
LaggedStart(*[
|
|
ReplacementTransform(
|
|
l0_copy.get_part_by_tex(tex),
|
|
lines[2].get_part_by_tex(tex),
|
|
path_arc=60 * DEGREES,
|
|
)
|
|
for tex in ["44", "\\over", "7", "approx", "2\\pi"]
|
|
], lag_ratio=0.1, run_time=2),
|
|
)
|
|
self.wait()
|
|
self.play(Transform(lines[2], lines[3]))
|
|
self.wait()
|
|
|
|
left_arithmetic = VGroup(rect, lines[:3])
|
|
self.play(
|
|
LaggedStart(
|
|
FadeOut(self.spiral_group[0]),
|
|
FadeOut(left_arithmetic),
|
|
FadeOut(self.right_arithmetic),
|
|
)
|
|
)
|
|
|
|
def count_by_44(self):
|
|
ff_label, ff_line = self.spiral_group[1:]
|
|
faded_labels = self.labels
|
|
frame = self.camera_frame
|
|
|
|
n_values = 100
|
|
mod = 44
|
|
values = range(mod, n_values * mod, mod)
|
|
points = [
|
|
self.get_polar_point(n, n)
|
|
for n in values
|
|
]
|
|
|
|
p2l_tracker = ValueTracker(
|
|
get_norm(ff_label.get_bottom() - points[0])
|
|
)
|
|
get_p2l = p2l_tracker.get_value
|
|
l_height_ratio_tracker = ValueTracker(
|
|
ff_label.get_height() / frame.get_height()
|
|
)
|
|
get_l_height_ratio = l_height_ratio_tracker.get_value
|
|
|
|
n_labels = 10
|
|
labels = VGroup(*[Integer(n) for n in values[:n_labels]])
|
|
for label, point in zip(labels, points):
|
|
label.point = point
|
|
label.add_updater(
|
|
lambda l: l.set_height(
|
|
frame.get_height() * get_l_height_ratio()
|
|
)
|
|
)
|
|
label.add_updater(
|
|
lambda l: l.move_to(
|
|
l.point + get_p2l() * UP,
|
|
DOWN,
|
|
)
|
|
)
|
|
labels.set_stroke(BLACK, 2, background=True)
|
|
|
|
lines = VGroup(ff_line)
|
|
for p1, p2 in zip(points, points[1:]):
|
|
lines.add(Line(p1, p2))
|
|
lines.match_style(ff_line)
|
|
|
|
self.remove(self.spiral_group)
|
|
self.remove(faded_labels[44])
|
|
self.play(
|
|
frame.scale, self.zoom_factor_1,
|
|
p2l_tracker.set_value, 1,
|
|
l_height_ratio_tracker.set_value, 0.025,
|
|
FadeOut(
|
|
ff_label,
|
|
rate_func=squish_rate_func(smooth, 0, 1 / 8),
|
|
),
|
|
LaggedStart(
|
|
*2 * [Animation(Group())], # Weird and dumb
|
|
*map(FadeIn, labels),
|
|
lag_ratio=0.5,
|
|
),
|
|
LaggedStart(
|
|
*2 * [Animation(Group())],
|
|
*map(ShowCreation, lines[:len(labels)]),
|
|
lag_ratio=1,
|
|
),
|
|
run_time=8,
|
|
)
|
|
self.play(
|
|
frame.scale, self.zoom_factor_2,
|
|
l_height_ratio_tracker.set_value, 0.01,
|
|
ShowCreation(lines[len(labels):]),
|
|
run_time=8,
|
|
)
|
|
|
|
self.ff_spiral_lines = lines
|
|
self.ff_spiral_labels = labels
|
|
|
|
#
|
|
def arc_func(self, t):
|
|
r = 0.1 * t
|
|
return r * np.array([np.cos(t), np.sin(t), 0])
|
|
|
|
|
|
class Label44Spirals(Explain44Spirals):
|
|
def construct(self):
|
|
self.setup_spirals()
|
|
self.enumerate_spirals()
|
|
|
|
def setup_spirals(self):
|
|
max_N = self.max_N
|
|
mod = 44
|
|
primes = read_in_primes(max_N)
|
|
spirals = VGroup()
|
|
for r in range(mod):
|
|
ns = range(r, max_N, mod)
|
|
spiral = self.get_v_spiral(ns, box_width=1)
|
|
for box, n in zip(spiral, ns):
|
|
box.n = n
|
|
if n in primes:
|
|
box.set_color(TEAL)
|
|
else:
|
|
box.set_color(YELLOW)
|
|
spirals.add(spiral)
|
|
|
|
self.add(spirals)
|
|
scale = np.prod([
|
|
self.initial_scale,
|
|
self.zoom_factor_1,
|
|
self.zoom_factor_2,
|
|
])
|
|
self.set_scale(
|
|
spiral=VGroup(*it.chain(*spirals)),
|
|
scale=scale,
|
|
run_time=0
|
|
)
|
|
|
|
self.spirals = spirals
|
|
|
|
def enumerate_spirals(self):
|
|
spirals = self.spirals
|
|
labels = self.get_spiral_arm_labels(spirals)
|
|
|
|
self.play(
|
|
spirals[1:].set_opacity, 0.25,
|
|
FadeIn(labels[0]),
|
|
)
|
|
self.wait()
|
|
|
|
for n in range(10):
|
|
arc = Arc(
|
|
start_angle=n + 0.2,
|
|
angle=0.9,
|
|
radius=1.5,
|
|
)
|
|
arc.add_tip()
|
|
mid_point = arc.point_from_proportion(0.5)
|
|
r_label = TextMobject("1 radian")
|
|
# r_label.rotate(
|
|
# angle_of_vector(arc.get_end() - arc.get_start()) - PI
|
|
|
|
# )
|
|
r_label.next_to(mid_point, normalize(mid_point))
|
|
|
|
self.play(
|
|
ShowCreation(arc),
|
|
FadeIn(r_label),
|
|
spirals[n + 1].set_opacity, 1,
|
|
TransformFromCopy(labels[n], labels[n + 1])
|
|
)
|
|
self.play(
|
|
FadeOut(arc),
|
|
FadeOut(r_label),
|
|
spirals[n].set_opacity, 0.25,
|
|
FadeOut(labels[n]),
|
|
)
|
|
|
|
#
|
|
def get_spiral_arm_labels(self, spirals, index=15):
|
|
mod = 44
|
|
labels = VGroup(*[
|
|
VGroup(
|
|
*TexMobject("44k", "+"),
|
|
Integer(n)
|
|
).arrange(RIGHT, buff=SMALL_BUFF)
|
|
for n in range(mod)
|
|
])
|
|
labels[0][1:].set_opacity(0)
|
|
labels.scale(1.5)
|
|
labels.set_color(YELLOW)
|
|
|
|
for label, spiral in zip(labels, spirals):
|
|
box = spiral[index]
|
|
vect = rotate_vector(box.get_center(), 90 * DEGREES)
|
|
label.next_to(box, normalize(vect), SMALL_BUFF)
|
|
labels[0].shift(UR + 1.25 * RIGHT)
|
|
return labels
|
|
|
|
|
|
class ResidueClassMod44Label(Scene):
|
|
def construct(self):
|
|
text = TextMobject(
|
|
"``Residue class mod 44''"
|
|
)
|
|
text.scale(2)
|
|
text.to_corner(UL)
|
|
|
|
self.play(Write(text))
|
|
self.wait()
|
|
|
|
|
|
class EliminateNonPrimativeResidueClassesOf44(Label44Spirals):
|
|
CONFIG = {
|
|
"max_N": 7000,
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_spirals()
|
|
self.eliminate_classes()
|
|
self.zoom_out()
|
|
self.filter_to_primes()
|
|
|
|
def eliminate_classes(self):
|
|
spirals = self.spirals
|
|
labels = self.get_spiral_arm_labels(spirals)
|
|
|
|
# Eliminate factors of 2
|
|
self.play(
|
|
spirals[1:].set_opacity, 0.5,
|
|
FadeIn(labels[0]),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(spirals[0]),
|
|
FadeOut(labels[0]),
|
|
)
|
|
for n in range(2, 8, 2):
|
|
self.play(
|
|
FadeIn(labels[n]),
|
|
spirals[n].set_opacity, 1,
|
|
)
|
|
self.play(FadeOut(VGroup(labels[n], spirals[n])))
|
|
|
|
words = TextMobject("All even numbers")
|
|
words.scale(1.5)
|
|
words.to_corner(UL)
|
|
self.play(
|
|
LaggedStart(*[
|
|
ApplyMethod(spiral.set_opacity, 1)
|
|
for spiral in spirals[8::2]
|
|
], lag_ratio=0.01),
|
|
FadeIn(words),
|
|
)
|
|
self.play(
|
|
FadeOut(words),
|
|
FadeOut(spirals[8::2])
|
|
)
|
|
self.wait()
|
|
|
|
# Eliminate factors of 11
|
|
for k in [11, 33]:
|
|
self.play(
|
|
spirals[k].set_opacity, 1,
|
|
FadeIn(labels[k])
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(spirals[k]),
|
|
FadeOut(labels[k]),
|
|
)
|
|
|
|
admissible_spirals = VGroup(*[
|
|
spiral
|
|
for n, spiral in enumerate(spirals)
|
|
if n % 2 != 0 and n % 11 != 0
|
|
|
|
])
|
|
|
|
self.play(admissible_spirals.set_opacity, 1)
|
|
|
|
self.admissible_spirals = admissible_spirals
|
|
|
|
def zoom_out(self):
|
|
frame = self.camera_frame
|
|
admissible_spirals = self.admissible_spirals
|
|
admissible_spirals.generate_target()
|
|
for spiral in admissible_spirals.target:
|
|
for box in spiral:
|
|
box.scale(3)
|
|
|
|
self.play(
|
|
frame.scale, 4,
|
|
MoveToTarget(admissible_spirals),
|
|
run_time=3,
|
|
)
|
|
|
|
def filter_to_primes(self):
|
|
admissible_spirals = self.admissible_spirals
|
|
frame = self.camera_frame
|
|
primes = read_in_primes(self.max_N)
|
|
|
|
to_fade = VGroup(*[
|
|
box
|
|
for spiral in admissible_spirals
|
|
for box in spiral
|
|
if box.n not in primes
|
|
])
|
|
words = TextMobject("Just the primes")
|
|
words.set_height(0.1 * frame.get_height())
|
|
words.next_to(frame.get_corner(UL), DR, LARGE_BUFF)
|
|
|
|
self.play(
|
|
LaggedStartMap(FadeOut, to_fade, lag_ratio=2 / len(to_fade)),
|
|
FadeIn(words),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class IntroduceTotientJargon(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.add_title()
|
|
self.eliminate_non_coprimes()
|
|
|
|
def add_title(self):
|
|
self.teacher_says(
|
|
"More jargon!",
|
|
target_mode="hooray",
|
|
)
|
|
self.change_all_student_modes("erm")
|
|
words = self.teacher.bubble.content
|
|
|
|
words.generate_target()
|
|
words.target.scale(1.5)
|
|
words.target.center().to_edge(UP, buff=MED_SMALL_BUFF)
|
|
words.target.set_color(BLUE)
|
|
underline = Line(LEFT, RIGHT)
|
|
underline.match_width(words.target)
|
|
underline.next_to(words.target, DOWN, SMALL_BUFF)
|
|
underline.scale(1.2)
|
|
|
|
self.play(
|
|
MoveToTarget(words),
|
|
FadeOut(self.teacher.bubble),
|
|
LaggedStart(*[
|
|
FadeOutAndShift(pi, 4 * DOWN)
|
|
for pi in self.pi_creatures
|
|
]),
|
|
ShowCreation(underline)
|
|
)
|
|
|
|
def eliminate_non_coprimes(self):
|
|
number_grid = VGroup(*[
|
|
VGroup(*[
|
|
Integer(n) for n in range(11 * k, 11 * (k + 1))
|
|
]).arrange(DOWN)
|
|
for k in range(4)
|
|
]).arrange(RIGHT, buff=1)
|
|
numbers = VGroup(*it.chain(*number_grid))
|
|
numbers.set_height(6)
|
|
numbers.move_to(4 * LEFT)
|
|
numbers.to_edge(DOWN)
|
|
|
|
evens = VGroup(*filter(
|
|
lambda nm: nm.get_value() % 2 == 0,
|
|
numbers
|
|
))
|
|
div11 = VGroup(*filter(
|
|
lambda nm: nm.get_value() % 11 == 0,
|
|
numbers
|
|
))
|
|
coprimes = VGroup(*filter(
|
|
lambda nm: nm not in evens and nm not in div11,
|
|
numbers
|
|
))
|
|
|
|
words = TextMobject(
|
|
"Which ones ", "don't\\\\",
|
|
"share any factors\\\\",
|
|
"with ", "44",
|
|
alignment=""
|
|
)
|
|
words.scale(1.5)
|
|
words.next_to(ORIGIN, RIGHT)
|
|
|
|
ff = words.get_part_by_tex("44")
|
|
ff.set_color(YELLOW)
|
|
ff.generate_target()
|
|
|
|
# Show coprimes
|
|
self.play(
|
|
ShowIncreasingSubsets(numbers, run_time=3),
|
|
FadeInFrom(words, LEFT)
|
|
)
|
|
self.wait()
|
|
for group in evens, div11:
|
|
rects = VGroup(*[
|
|
SurroundingRectangle(number, color=RED)
|
|
for number in group
|
|
])
|
|
self.play(LaggedStartMap(ShowCreation, rects, run_time=1))
|
|
self.play(
|
|
LaggedStart(*[
|
|
ApplyMethod(number.set_opacity, 0.2)
|
|
for number in group
|
|
]),
|
|
LaggedStartMap(FadeOut, rects),
|
|
run_time=1
|
|
)
|
|
self.wait()
|
|
|
|
# Rearrange words
|
|
dsf = words[1:3]
|
|
dsf.generate_target()
|
|
dsf.target.arrange(RIGHT)
|
|
dsf.target[0].align_to(dsf.target[1][0], DOWN)
|
|
|
|
example = numbers[35].copy()
|
|
example.generate_target()
|
|
example.target.match_height(ff)
|
|
num_pair = VGroup(
|
|
ff.target,
|
|
TextMobject("and").scale(1.5),
|
|
example.target,
|
|
)
|
|
num_pair.arrange(RIGHT)
|
|
num_pair.move_to(words.get_top(), DOWN)
|
|
dsf.target.next_to(num_pair, DOWN, MED_LARGE_BUFF)
|
|
|
|
phrase1 = TextMobject("are ", "``relatively prime''")
|
|
phrase2 = TextMobject("are ", "``coprime''")
|
|
for phrase in phrase1, phrase2:
|
|
phrase.scale(1.5)
|
|
phrase.move_to(dsf.target)
|
|
phrase[1].set_color(BLUE)
|
|
phrase.arrow = TexMobject("\\Updownarrow")
|
|
phrase.arrow.scale(1.5)
|
|
phrase.arrow.next_to(phrase, DOWN, 2 * SMALL_BUFF)
|
|
phrase.rect = SurroundingRectangle(phrase[1])
|
|
phrase.rect.set_stroke(BLUE)
|
|
|
|
self.play(
|
|
FadeOut(words[0]),
|
|
FadeOut(words[3]),
|
|
MoveToTarget(dsf),
|
|
MoveToTarget(ff),
|
|
GrowFromCenter(num_pair[1]),
|
|
)
|
|
self.play(
|
|
MoveToTarget(example, path_arc=30 * DEGREES),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
dsf.next_to, phrase1.arrow, DOWN, SMALL_BUFF,
|
|
GrowFromEdge(phrase1.arrow, UP),
|
|
GrowFromCenter(phrase1),
|
|
ShowCreation(phrase1.rect)
|
|
)
|
|
self.play(FadeOut(phrase1.rect))
|
|
self.wait()
|
|
self.play(
|
|
VGroup(dsf, phrase1, phrase1.arrow).next_to,
|
|
phrase2.arrow, DOWN, SMALL_BUFF,
|
|
GrowFromEdge(phrase2.arrow, UP),
|
|
GrowFromCenter(phrase2),
|
|
ShowCreation(phrase2.rect)
|
|
)
|
|
self.play(FadeOut(phrase2.rect))
|
|
self.wait()
|
|
|
|
# Count through coprimes
|
|
coprime_rects = VGroup(*map(SurroundingRectangle, coprimes))
|
|
coprime_rects.set_stroke(BLUE, 2)
|
|
example_anim = UpdateFromFunc(
|
|
example, lambda m: m.set_value(coprimes[len(coprime_rects) - 1].get_value())
|
|
)
|
|
self.play(
|
|
ShowIncreasingSubsets(coprime_rects, int_func=np.ceil),
|
|
example_anim,
|
|
run_time=3,
|
|
rate_func=linear,
|
|
)
|
|
self.wait()
|
|
|
|
# Show totient function
|
|
words_to_keep = VGroup(ff, num_pair[1], example, phrase2)
|
|
to_fade = VGroup(phrase2.arrow, phrase1, phrase1.arrow, dsf)
|
|
|
|
totient = TexMobject("\\phi", "(", "44", ")", "=", "20")
|
|
totient.set_color_by_tex("44", YELLOW)
|
|
totient.scale(1.5)
|
|
totient.move_to(num_pair, UP)
|
|
phi = totient.get_part_by_tex("phi")
|
|
rhs = Integer(20)
|
|
rhs.replace(totient[-1], dim_to_match=1)
|
|
totient.submobjects[-1] = rhs
|
|
|
|
self.play(
|
|
words_to_keep.to_edge, DOWN,
|
|
MaintainPositionRelativeTo(to_fade, words_to_keep),
|
|
VFadeOut(to_fade),
|
|
)
|
|
self.play(FadeIn(totient))
|
|
self.wait()
|
|
|
|
# Label totient
|
|
brace = Brace(phi, DOWN)
|
|
etf = TextMobject("Euler's totient function")
|
|
etf.next_to(brace, DOWN)
|
|
etf.shift(RIGHT)
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeInFrom(etf, UP)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowIncreasingSubsets(coprime_rects),
|
|
UpdateFromFunc(
|
|
rhs, lambda m: m.set_value(len(coprime_rects)),
|
|
),
|
|
example_anim,
|
|
rate_func=linear,
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
|
|
# Show totatives
|
|
totient_group = VGroup(totient, brace, etf)
|
|
for cp, rect in zip(coprimes, coprime_rects):
|
|
cp.add(rect)
|
|
|
|
self.play(
|
|
coprimes.arrange, RIGHT, {"buff": SMALL_BUFF},
|
|
coprimes.set_width, FRAME_WIDTH - 1,
|
|
coprimes.move_to, 2 * UP,
|
|
FadeOut(evens),
|
|
FadeOut(div11[1::2]),
|
|
FadeOutAndShiftDown(words_to_keep),
|
|
totient_group.center,
|
|
totient_group.to_edge, DOWN,
|
|
)
|
|
|
|
totatives = TextMobject("``Totatives''")
|
|
totatives.scale(2)
|
|
totatives.set_color(BLUE)
|
|
totatives.move_to(ORIGIN)
|
|
arrows = VGroup(*[
|
|
Arrow(totatives.get_top(), coprime.get_bottom())
|
|
for coprime in coprimes
|
|
])
|
|
arrows.set_color(WHITE)
|
|
|
|
self.play(
|
|
FadeIn(totatives),
|
|
LaggedStartMap(VFadeInThenOut, arrows, run_time=4, lag_ratio=0.05)
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class TwoUnrelatedFacts(Scene):
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_columns()
|
|
|
|
def add_title(self):
|
|
title = TextMobject("Two (unrelated) bits of number theory")
|
|
title.set_width(FRAME_WIDTH - 1)
|
|
title.to_edge(UP)
|
|
h_line = Line()
|
|
h_line.match_width(title)
|
|
h_line.next_to(title, DOWN, SMALL_BUFF)
|
|
h_line.set_stroke(LIGHT_GREY)
|
|
|
|
self.play(
|
|
FadeIn(title),
|
|
ShowCreation(h_line),
|
|
)
|
|
|
|
self.h_line = h_line
|
|
|
|
def show_columns(self):
|
|
h_line = self.h_line
|
|
v_line = Line(
|
|
h_line.get_center() + MED_SMALL_BUFF * DOWN,
|
|
FRAME_HEIGHT * DOWN / 2,
|
|
)
|
|
v_line.match_style(h_line)
|
|
|
|
approx = TexMobject(
|
|
"{44 \\over 7} \\approx 2\\pi"
|
|
)
|
|
approx.scale(1.5)
|
|
approx.next_to(
|
|
h_line.point_from_proportion(0.25),
|
|
DOWN, MED_LARGE_BUFF,
|
|
)
|
|
|
|
mod = 44
|
|
n_terms = 9
|
|
residue_classes = VGroup()
|
|
prime_numbers = read_in_primes(1000)
|
|
primes = VGroup()
|
|
non_primes = VGroup()
|
|
for r in range(mod):
|
|
if r <= 11 or r == 43:
|
|
row = VGroup()
|
|
for n in range(r, r + n_terms * mod, mod):
|
|
elem = Integer(n)
|
|
comma = TexMobject(",")
|
|
comma.next_to(
|
|
elem.get_corner(DR),
|
|
RIGHT, SMALL_BUFF
|
|
)
|
|
elem.add(comma)
|
|
row.add(elem)
|
|
if n in prime_numbers:
|
|
primes.add(elem)
|
|
else:
|
|
non_primes.add(elem)
|
|
row.arrange(RIGHT, buff=0.3)
|
|
dots = TexMobject("\\dots")
|
|
dots.next_to(row.get_corner(DR), RIGHT, SMALL_BUFF)
|
|
dots.shift(SMALL_BUFF * UP)
|
|
row.add(dots)
|
|
row.r = r
|
|
if r == 12:
|
|
row = TexMobject("\\vdots")
|
|
residue_classes.add(row)
|
|
|
|
residue_classes.arrange(DOWN)
|
|
residue_classes[-2].align_to(residue_classes, LEFT)
|
|
residue_classes[-2].shift(MED_SMALL_BUFF * RIGHT)
|
|
residue_classes.set_height(6)
|
|
residue_classes.next_to(ORIGIN, RIGHT)
|
|
residue_classes.to_edge(DOWN, buff=MED_SMALL_BUFF)
|
|
|
|
def get_line(row):
|
|
return Line(
|
|
row.get_left(), row.get_right(),
|
|
stroke_color=RED,
|
|
stroke_width=4,
|
|
)
|
|
|
|
even_lines = VGroup(*[
|
|
get_line(row)
|
|
for row in residue_classes[:12:2]
|
|
])
|
|
eleven_line = get_line(residue_classes[11])
|
|
eleven_line.set_color(PINK)
|
|
for line in [even_lines[1], eleven_line]:
|
|
line.scale(0.93, about_edge=RIGHT)
|
|
|
|
self.play(ShowCreation(v_line))
|
|
self.wait()
|
|
self.play(FadeInFrom(approx, DOWN))
|
|
self.wait()
|
|
self.play(FadeIn(residue_classes))
|
|
self.wait()
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, even_lines),
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(eleven_line))
|
|
self.wait()
|
|
self.play(
|
|
primes.set_color, TEAL,
|
|
non_primes.set_opacity, 0.25,
|
|
even_lines.set_opacity, 0.25,
|
|
eleven_line.set_opacity, 0.25,
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class ExplainRays(Explain44Spirals):
|
|
CONFIG = {
|
|
"max_N": int(5e5),
|
|
"axes_config": {
|
|
"x_min": -1000,
|
|
"x_max": 1000,
|
|
"y_min": -1000,
|
|
"y_max": 1000,
|
|
"number_line_config": {
|
|
"tick_frequency": 50,
|
|
},
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_spirals_and_labels()
|
|
self.show_710th_point()
|
|
self.show_arithmetic()
|
|
self.zoom_and_count()
|
|
|
|
def show_710th_point(self):
|
|
spiral = self.spiral
|
|
axes = self.axes
|
|
labels = self.labels
|
|
|
|
scale_factor = 12
|
|
|
|
fade_rect = FullScreenFadeRectangle()
|
|
fade_rect.scale(scale_factor)
|
|
|
|
new_ns = list(range(711))
|
|
bright_boxes = self.get_v_spiral(new_ns)
|
|
bright_boxes.set_color(YELLOW)
|
|
for n, box in enumerate(bright_boxes):
|
|
box.set_height(0.02 * np.sqrt(n))
|
|
|
|
big_labels = self.get_labels(new_ns)
|
|
|
|
index_tracker = ValueTracker(44)
|
|
|
|
labeled_box = VGroup(Square(), Integer(0))
|
|
|
|
def update_labeled_box(mob):
|
|
index = int(index_tracker.get_value())
|
|
labeled_box[0].become(bright_boxes[index])
|
|
labeled_box[1].become(big_labels[index])
|
|
|
|
labeled_box.add_updater(update_labeled_box)
|
|
|
|
self.set_scale(
|
|
scale=120,
|
|
spiral=spiral,
|
|
to_shrink=labels,
|
|
)
|
|
|
|
box_710 = self.get_v_spiral([710])[0]
|
|
box_710.scale(2)
|
|
box_710.set_color(YELLOW)
|
|
label_710 = Integer(710)
|
|
label_710.scale(1.5)
|
|
label_710.next_to(box_710, UP)
|
|
|
|
arrow = Arrow(
|
|
ORIGIN, DOWN,
|
|
stroke_width=6,
|
|
max_tip_length_to_length_ratio=0.35,
|
|
max_stroke_width_to_length_ratio=10,
|
|
tip_length=0.35
|
|
)
|
|
arrow.match_color(box_710)
|
|
arrow.next_to(box_710, UP, SMALL_BUFF)
|
|
label_710.next_to(arrow, UP, SMALL_BUFF)
|
|
|
|
self.add(spiral, fade_rect, axes, labels)
|
|
self.play(
|
|
FadeIn(fade_rect),
|
|
FadeOut(labels),
|
|
FadeInFromLarge(box_710),
|
|
FadeInFrom(label_710, DOWN),
|
|
ShowCreation(arrow),
|
|
)
|
|
self.wait()
|
|
|
|
self.fade_rect = fade_rect
|
|
self.box_710 = box_710
|
|
self.label_710 = label_710
|
|
self.arrow = arrow
|
|
|
|
def show_arithmetic(self):
|
|
label_710 = self.label_710
|
|
|
|
equation = TexMobject(
|
|
"710", "\\text{ radians}", "=",
|
|
"(710 / 2\\pi)", "\\text{ rotations}",
|
|
)
|
|
equation.to_corner(UL)
|
|
frac = equation.get_part_by_tex("710 / 2\\pi")
|
|
brace = Brace(frac, DOWN, buff=SMALL_BUFF)
|
|
value = TextMobject("{:.15}".format(710 / TAU))
|
|
value.next_to(brace, DOWN, SMALL_BUFF)
|
|
values = VGroup(*[
|
|
value[0][:n].deepcopy().next_to(brace, DOWN, SMALL_BUFF)
|
|
for n in [3, *range(5, 13)]
|
|
])
|
|
|
|
group = VGroup(equation, brace, value)
|
|
rect = SurroundingRectangle(group, buff=MED_SMALL_BUFF)
|
|
rect.set_stroke(WHITE, 2)
|
|
rect.set_fill(DARK_GREY, 1)
|
|
|
|
approx = TexMobject(
|
|
"{710", "\\over", "113}",
|
|
"\\approx", "2\\pi",
|
|
)
|
|
approx.next_to(rect, DOWN)
|
|
approx.align_to(equation, LEFT)
|
|
|
|
approx2 = TexMobject(
|
|
"{355", "\\over", "113}",
|
|
"\\approx", "\\pi",
|
|
)
|
|
approx2.next_to(approx, RIGHT, LARGE_BUFF)
|
|
|
|
self.play(
|
|
FadeIn(rect),
|
|
TransformFromCopy(label_710, equation[0]),
|
|
FadeIn(equation[1:3]),
|
|
)
|
|
self.play(
|
|
FadeInFrom(equation[3:], LEFT)
|
|
)
|
|
self.play(GrowFromCenter(brace))
|
|
self.play(
|
|
ShowSubmobjectsOneByOne(values),
|
|
run_time=3,
|
|
rate_func=linear,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
rect.stretch, 2, 1, {"about_edge": UP},
|
|
LaggedStart(
|
|
TransformFromCopy( # 710
|
|
equation[3][1:4],
|
|
approx[0],
|
|
),
|
|
FadeIn(approx[1][0]),
|
|
TransformFromCopy( # 113
|
|
values[-1][:3],
|
|
approx[2],
|
|
),
|
|
FadeIn(approx[3]),
|
|
TransformFromCopy( # 2pi
|
|
equation[3][5:7],
|
|
approx[4],
|
|
),
|
|
run_time=2,
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(approx, approx2),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(VGroup(
|
|
rect, equation, brace, values[-1],
|
|
approx, approx2
|
|
)),
|
|
self.fade_rect.set_opacity, 0.25,
|
|
)
|
|
|
|
def zoom_and_count(self):
|
|
label = self.label_710
|
|
arrow = self.arrow
|
|
box = self.box_710
|
|
axes = self.axes
|
|
spiral = self.spiral
|
|
|
|
times = TexMobject("\\times")
|
|
times.next_to(label, LEFT, SMALL_BUFF)
|
|
k_label = Integer(1)
|
|
k_label.match_height(label)
|
|
k_label.set_color(YELLOW)
|
|
k_label.next_to(times, LEFT)
|
|
|
|
boxes = VGroup(*[box.copy() for x in range(150)])
|
|
box_height_tracker = ValueTracker(box.get_height())
|
|
|
|
def get_k():
|
|
max_x = axes.x_axis.p2n(label.get_center())
|
|
return max(1, int(max_x / 710))
|
|
|
|
def get_k_point(k):
|
|
return self.get_polar_point(710 * k, 710 * k)
|
|
|
|
def update_arrow(arrow):
|
|
point = get_k_point(get_k())
|
|
arrow.put_start_and_end_on(
|
|
label.get_bottom() + SMALL_BUFF * DOWN,
|
|
point + SMALL_BUFF * UP
|
|
)
|
|
|
|
def get_unit():
|
|
return get_norm(axes.c2p(1, 0) - axes.c2p(0, 0))
|
|
|
|
def update_boxes(boxes):
|
|
box_height = box_height_tracker.get_value()
|
|
for k, box in enumerate(boxes):
|
|
box.set_height(box_height)
|
|
box.move_to(get_k_point(k))
|
|
|
|
arrow.add_updater(update_arrow)
|
|
boxes.add_updater(update_boxes)
|
|
k_label.add_updater(
|
|
lambda d: d.set_value(get_k()).next_to(
|
|
times, LEFT, SMALL_BUFF
|
|
)
|
|
)
|
|
|
|
self.remove(box)
|
|
self.add(times, k_label, boxes)
|
|
self.set_scale(
|
|
scale=10000,
|
|
spiral=self.spiral,
|
|
run_time=8,
|
|
target_p_spiral_width=2,
|
|
added_anims=[
|
|
box_height_tracker.set_value, 0.035,
|
|
]
|
|
)
|
|
self.wait()
|
|
|
|
# Show other residue classes
|
|
new_label = TexMobject(
|
|
"710", "k", "+",
|
|
tex_to_color_map={"k": YELLOW}
|
|
)
|
|
new_label.match_height(label)
|
|
new_label.next_to(boxes, UP, SMALL_BUFF)
|
|
new_label.to_edge(RIGHT)
|
|
new_label[2].set_opacity(0)
|
|
|
|
r_label = Integer(1)
|
|
r_label.match_height(new_label)
|
|
r_label.set_opacity(0)
|
|
r_label.add_updater(
|
|
lambda m: m.next_to(new_label, RIGHT, SMALL_BUFF)
|
|
)
|
|
|
|
k_label.clear_updaters()
|
|
self.play(
|
|
FadeOut(times),
|
|
ReplacementTransform(label, new_label[0]),
|
|
ReplacementTransform(k_label, new_label[1]),
|
|
FadeOut(arrow)
|
|
)
|
|
|
|
boxes.clear_updaters()
|
|
for r in range(1, 12):
|
|
if r in [3, 6]:
|
|
vect = UR
|
|
else:
|
|
vect = RIGHT
|
|
point = rotate_vector(boxes[40].get_center(), 1)
|
|
new_boxes = boxes.copy()
|
|
new_boxes.rotate(1, about_point=ORIGIN)
|
|
for box in new_boxes:
|
|
box.rotate(-1)
|
|
self.play(
|
|
FadeOut(boxes),
|
|
LaggedStartMap(FadeIn, new_boxes, lag_ratio=0.01),
|
|
new_label.set_opacity, 1,
|
|
new_label.next_to, point, vect,
|
|
r_label.set_opacity, 1,
|
|
ChangeDecimalToValue(r_label, r),
|
|
run_time=1,
|
|
)
|
|
self.remove(boxes)
|
|
boxes = new_boxes
|
|
self.add(boxes)
|
|
self.wait()
|
|
|
|
# Show just the primes
|
|
self.play(
|
|
FadeOut(boxes),
|
|
FadeOut(new_label),
|
|
FadeOut(r_label),
|
|
FadeOut(self.fade_rect)
|
|
)
|
|
self.set_scale(
|
|
30000,
|
|
spiral=spiral,
|
|
run_time=4,
|
|
)
|
|
self.wait()
|
|
self.remove(spiral)
|
|
self.add(spiral[1])
|
|
self.wait()
|
|
|
|
|
|
class CompareTauToApprox(Scene):
|
|
def construct(self):
|
|
eqs = VGroup(
|
|
TexMobject("2\\pi", "=", "{:.10}\\dots".format(TAU)),
|
|
TexMobject("\\frac{710}{113}", "=", "{:.10}\\dots".format(710 / 113)),
|
|
)
|
|
eqs.arrange(DOWN, buff=LARGE_BUFF)
|
|
eqs[1].shift((eqs[0][2].get_left()[0] - eqs[1][2].get_left()[0]) * RIGHT)
|
|
|
|
eqs.generate_target()
|
|
for eq in eqs.target:
|
|
eq[2][:8].set_color(RED)
|
|
eq.set_stroke(BLACK, 8, background=True)
|
|
|
|
self.play(LaggedStart(
|
|
FadeInFrom(eqs[0], DOWN),
|
|
FadeInFrom(eqs[1], UP),
|
|
))
|
|
self.play(MoveToTarget(eqs))
|
|
self.wait()
|
|
|
|
|
|
class RecommendedMathologerVideo(Scene):
|
|
def construct(self):
|
|
full_rect = FullScreenFadeRectangle()
|
|
full_rect.set_fill(DARK_GREY, 1)
|
|
self.add(full_rect)
|
|
|
|
title = TextMobject("Recommended Mathologer video")
|
|
title.set_width(FRAME_WIDTH - 1)
|
|
title.to_edge(UP)
|
|
screen_rect = SurroundingRectangle(ScreenRectangle(height=5.9), buff=SMALL_BUFF)
|
|
screen_rect.next_to(title, DOWN)
|
|
screen_rect.set_fill(BLACK, 1)
|
|
screen_rect.set_stroke(WHITE, 3)
|
|
|
|
self.add(screen_rect)
|
|
self.play(Write(title))
|
|
self.wait()
|
|
|
|
|
|
class ShowClassesOfPrimeRays(SpiralScene):
|
|
CONFIG = {
|
|
"max_N": int(1e6),
|
|
"scale": 1e5
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_rays()
|
|
self.show_classes()
|
|
|
|
def setup_rays(self):
|
|
spiral = self.get_prime_p_spiral(self.max_N)
|
|
self.add(spiral)
|
|
self.set_scale(
|
|
scale=self.scale,
|
|
spiral=spiral,
|
|
target_p_spiral_width=3,
|
|
run_time=0
|
|
)
|
|
|
|
def show_classes(self):
|
|
max_N = self.max_N
|
|
mod = 710
|
|
primes = read_in_primes(max_N)
|
|
|
|
rect = FullScreenFadeRectangle()
|
|
rect.set_opacity(0)
|
|
self.add(rect)
|
|
|
|
last_ray = PMobject()
|
|
last_label = VGroup(*[VectorizedPoint() for x in range(3)])
|
|
for i in range(40):
|
|
if get_gcd(i, mod) != 1:
|
|
continue
|
|
|
|
r = (INV_113_MOD_710 * i) % mod
|
|
|
|
sequence = filter(
|
|
lambda x: x in primes,
|
|
range(r, max_N, mod)
|
|
)
|
|
ray = self.get_v_spiral(sequence, box_width=0.03)
|
|
ray.set_color(GREEN)
|
|
ray.set_opacity(0.9)
|
|
|
|
label = VGroup(
|
|
*TexMobject("710k", "+"),
|
|
Integer(r)
|
|
)
|
|
label.arrange(RIGHT, buff=SMALL_BUFF)
|
|
label.next_to(ray[100], UL, SMALL_BUFF)
|
|
|
|
label[2].save_state()
|
|
label[2].set_opacity(0)
|
|
label[2].move_to(last_label, RIGHT)
|
|
|
|
self.play(
|
|
rect.set_opacity, 0.5,
|
|
ShowCreation(ray),
|
|
LaggedStartMap(FadeOut, last_ray),
|
|
ReplacementTransform(last_label[:2], label[:2]),
|
|
Restore(label[2]),
|
|
last_label[2].move_to, label[2].saved_state,
|
|
last_label[2].set_opacity, 0,
|
|
)
|
|
self.remove(last_label)
|
|
self.add(label)
|
|
self.wait()
|
|
|
|
last_ray = ray
|
|
last_label = label
|
|
|
|
|
|
class ShowFactorsOf710(Scene):
|
|
def construct(self):
|
|
equation = TexMobject(
|
|
"710", "=",
|
|
"71", "\\cdot",
|
|
"5", "\\cdot",
|
|
"2",
|
|
)
|
|
equation.scale(1.5)
|
|
equation.to_corner(UL)
|
|
ten = TexMobject("10")
|
|
ten.match_height(equation)
|
|
ten.move_to(equation.get_part_by_tex("5"), LEFT)
|
|
|
|
self.add(equation[0])
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(
|
|
equation[0][:2],
|
|
equation[2],
|
|
),
|
|
FadeIn(equation[1]),
|
|
FadeIn(equation[3]),
|
|
TransformFromCopy(
|
|
equation[0][2:],
|
|
ten,
|
|
)
|
|
)
|
|
self.wait()
|
|
self.remove(ten)
|
|
self.play(*[
|
|
TransformFromCopy(ten, mob)
|
|
for mob in equation[4:]
|
|
])
|
|
self.wait()
|
|
|
|
# Circle factors
|
|
colors = [RED, BLUE, PINK]
|
|
for factor, color in zip(equation[:-6:-2], colors):
|
|
rect = SurroundingRectangle(factor)
|
|
rect.set_color(color)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
self.play(FadeOut(rect))
|
|
|
|
|
|
class LookAtRemainderMod710(Scene):
|
|
def construct(self):
|
|
t2c = {
|
|
"n": YELLOW,
|
|
"r": GREEN
|
|
}
|
|
equation = TexMobject(
|
|
"n", "=", "710", "k", "+", "r",
|
|
tex_to_color_map=t2c,
|
|
)
|
|
equation.scale(1.5)
|
|
|
|
n_arrow = Vector(UP).next_to(equation.get_part_by_tex("n"), DOWN)
|
|
r_arrow = Vector(UP).next_to(equation.get_part_by_tex("r"), DOWN)
|
|
n_arrow.set_color(t2c["n"])
|
|
r_arrow.set_color(t2c["r"])
|
|
|
|
n_label = TextMobject("Some\\\\number")
|
|
r_label = TextMobject("Remainder")
|
|
VGroup(n_label, r_label).scale(1.5)
|
|
n_label.next_to(n_arrow, DOWN)
|
|
n_label.match_color(n_arrow)
|
|
r_label.next_to(r_arrow, DOWN)
|
|
r_label.match_color(r_arrow)
|
|
|
|
self.add(equation)
|
|
self.play(
|
|
FadeInFrom(n_label, UP),
|
|
ShowCreation(n_arrow),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFrom(r_label, DOWN),
|
|
ShowCreation(r_arrow),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class EliminateNonPrimative710Residues(ShowClassesOfPrimeRays):
|
|
CONFIG = {
|
|
"max_N": int(5e5),
|
|
"scale": 5e4,
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_rays()
|
|
self.eliminate_classes()
|
|
|
|
def setup_rays(self):
|
|
mod = 710
|
|
rays = PGroup(*[
|
|
self.get_p_spiral(range(r, self.max_N, mod))
|
|
for r in range(mod)
|
|
])
|
|
rays.set_color(YELLOW)
|
|
self.add(rays)
|
|
self.set_scale(
|
|
scale=self.scale,
|
|
spiral=rays,
|
|
target_p_spiral_width=1,
|
|
run_time=0,
|
|
)
|
|
|
|
self.rays = rays
|
|
|
|
def eliminate_classes(self):
|
|
rays = self.rays
|
|
rect = FullScreenFadeRectangle()
|
|
rect.set_opacity(0)
|
|
|
|
for r, ray in enumerate(rays):
|
|
ray.r = r
|
|
|
|
mod = 710
|
|
odds = PGroup(*[rays[i] for i in range(1, mod, 2)])
|
|
mult5 = PGroup(*[rays[i] for i in range(0, mod, 5) if i % 2 != 0])
|
|
mult71 = PGroup(*[rays[i] for i in range(0, mod, 71) if (i % 2 != 0 and i % 5 != 0)])
|
|
colors = [RED, BLUE, PINK]
|
|
|
|
pre_label, r_label = label = VGroup(
|
|
TexMobject("710k + "),
|
|
Integer(100)
|
|
)
|
|
label.scale(1.5)
|
|
label.arrange(RIGHT, buff=SMALL_BUFF)
|
|
label.set_stroke(BLACK, 5, background=True, family=True)
|
|
label.next_to(ORIGIN, DOWN)
|
|
|
|
r_label.group = odds
|
|
r_label.add_updater(
|
|
lambda m: m.set_value(m.group[-1].r if len(m.group) > 0 else 1),
|
|
)
|
|
|
|
self.remove(rays)
|
|
# Odds
|
|
odds.set_stroke_width(3)
|
|
self.add(odds, label)
|
|
self.play(
|
|
ShowIncreasingSubsets(odds, int_func=np.ceil),
|
|
run_time=10,
|
|
)
|
|
self.play(FadeOut(label))
|
|
self.remove(odds)
|
|
self.add(*odds)
|
|
self.wait()
|
|
|
|
# Multiples of 5 then 71
|
|
for i, group in [(1, mult5), (2, mult71)]:
|
|
group_copy = group.copy()
|
|
group_copy.set_color(colors[i])
|
|
group_copy.set_stroke_width(4)
|
|
r_label.group = group_copy
|
|
self.add(group_copy, label)
|
|
self.play(
|
|
ShowIncreasingSubsets(group_copy, int_func=np.ceil, run_time=10),
|
|
)
|
|
self.play(FadeOut(label))
|
|
self.wait()
|
|
self.remove(group_copy, *group)
|
|
self.wait()
|
|
|
|
|
|
class Show280Computation(Scene):
|
|
def construct(self):
|
|
equation = TexMobject(
|
|
"\\phi(710) = ",
|
|
"710",
|
|
"\\left({1 \\over 2}\\right)",
|
|
"\\left({4 \\over 5}\\right)",
|
|
"\\left({70 \\over 71}\\right)",
|
|
"=",
|
|
"280",
|
|
)
|
|
equation.set_width(FRAME_WIDTH - 1)
|
|
equation.move_to(UP)
|
|
words = VGroup(
|
|
TextMobject("Filter out\\\\evens"),
|
|
TextMobject("Filter out\\\\multiples of 5"),
|
|
TextMobject("Filter out\\\\multiples of 71"),
|
|
)
|
|
vects = [DOWN, UP, DOWN]
|
|
colors = [RED, BLUE, LIGHT_PINK]
|
|
for part, word, vect, color in zip(equation[2:5], words, vects, colors):
|
|
brace = Brace(part, vect)
|
|
brace.stretch(0.8, 0)
|
|
word.brace = brace
|
|
word.next_to(brace, vect)
|
|
part.set_color(color)
|
|
word.set_color(color)
|
|
word.set_stroke(BLACK, 5, background=True)
|
|
equation.set_stroke(BLACK, 5, background=True)
|
|
|
|
rect = FullScreenFadeRectangle(fill_opacity=0.9)
|
|
self.play(
|
|
FadeIn(rect),
|
|
FadeInFromDown(equation),
|
|
)
|
|
self.wait()
|
|
for word in words:
|
|
self.play(
|
|
FadeIn(word),
|
|
GrowFromCenter(word.brace),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class TeacherHoldUp(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.change_all_student_modes(
|
|
"pondering", look_at_arg=2 * UP,
|
|
added_anims=[
|
|
self.teacher.change, "raise_right_hand"
|
|
]
|
|
)
|
|
self.wait(4)
|
|
|
|
|
|
class DiscussPrimesMod10(Scene):
|
|
def construct(self):
|
|
pass
|
|
|
|
|
|
class BucketPrimesByLastDigit(Scene):
|
|
def construct(self):
|
|
pass
|