Files
manim/active_projects/spirals.py
2019-10-03 13:58:27 -07:00

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