Files
manim/3b1b_projects/old/spirals.py

4932 lines
139 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)
words.set_stroke(BLACK, 8, background=True)
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 TopQuestionCross(Scene):
def construct(self):
top_q = TextMobject("Why do", " primes", " cause", " spirals", "?")
top_q.scale(2)
top_q.to_edge(UP)
cross = Cross(top_q)
self.play(ShowCreation(cross))
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 = 3
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()
self.wait(6)
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))
if n > 2:
r_label.set_opacity(0)
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)
etf_label = TextMobject("Euler's totient function")
etf_label.to_corner(UL)
arrow = Arrow(etf_label.get_bottom(), equation[0][0].get_top())
equation[0][0].set_color(YELLOW)
etf_label.set_color(YELLOW)
rect = FullScreenFadeRectangle(fill_opacity=0.9)
self.play(
FadeIn(rect),
FadeInFromDown(equation),
FadeIn(etf_label),
GrowArrow(arrow),
)
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(8)
class DiscussPrimesMod10(Scene):
def construct(self):
labels = VGroup(*[
TextMobject(str(n), " mod 10:")
for n in range(10)
])
labels.arrange(DOWN, buff=0.35, aligned_edge=LEFT)
labels.to_edge(LEFT)
# digits = VGroup(*[l[0] for l in labels])
labels.set_submobject_colors_by_gradient(YELLOW, BLUE)
sequences = VGroup(*[
VGroup(*[
Integer(n).shift((n // 10) * RIGHT)
for n in range(r, 100 + r, 10)
])
for r in range(10)
])
for sequence, label in zip(sequences, labels):
sequence.next_to(label, RIGHT, buff=MED_LARGE_BUFF)
for item in sequence:
if item is sequence[-1]:
punc = TexMobject("\\dots")
else:
punc = TextMobject(",")
punc.next_to(item.get_corner(DR), RIGHT, SMALL_BUFF)
item.add(punc)
# Introduce everything
self.play(LaggedStart(*[
FadeInFrom(label, UP)
for label in labels
]))
self.wait()
self.play(
LaggedStart(*[
LaggedStart(*[
FadeInFrom(item, LEFT)
for item in sequence
])
for sequence in sequences
])
)
self.wait()
# Highlight 0's then 1's
for sequence in sequences[:2]:
lds = VGroup(*[item[-2] for item in sequence])
rects = VGroup(*[
SurroundingRectangle(ld, buff=0.05)
for ld in lds
])
rects.set_color(YELLOW)
self.play(
LaggedStartMap(
ShowCreationThenFadeOut, rects
)
)
self.wait()
# Eliminate certain residues
two = sequences[2][0]
five = sequences[5][0]
evens = VGroup(*it.chain(*sequences[::2]))
evens.remove(two)
div5 = sequences[5][1:]
prime_numbers = read_in_primes(100)
primes = VGroup(*[
item
for seq in sequences
for item in seq
if int(item.get_value()) in prime_numbers
])
non_primes = VGroup(*[
item
for seq in sequences
for item in seq
if reduce(op.and_, [
int(item.get_value()) not in prime_numbers,
item.get_value() % 2 != 0,
item.get_value() % 5 != 0,
])
])
for prime, group in [(two, evens), (five, div5)]:
self.play(ShowCreationThenFadeAround(prime))
self.play(LaggedStart(*[
ApplyMethod(item.set_opacity, 0.2)
for item in group
]))
self.wait()
# Highlight primes
self.play(
LaggedStart(*[
ApplyFunction(
lambda m: m.scale(1.2).set_color(TEAL),
prime
)
for prime in primes
]),
LaggedStart(*[
ApplyFunction(
lambda m: m.scale(0.8).set_opacity(0.8),
non_prime
)
for non_prime in non_primes
]),
)
self.wait()
# Highlight coprime residue classes
rects = VGroup(*[
SurroundingRectangle(VGroup(labels[r], sequences[r]))
for r in [1, 3, 7, 9]
])
for rect in rects:
rect.reverse_points()
fade_rect = FullScreenFadeRectangle()
fade_rect.scale(1.1)
new_fade_rect = fade_rect.copy()
fade_rect.append_vectorized_mobject(rects[0])
for rect in rects:
new_fade_rect.append_vectorized_mobject(rect)
self.play(DrawBorderThenFill(fade_rect))
self.wait()
self.play(
FadeOut(fade_rect),
FadeIn(new_fade_rect),
)
self.wait()
class BucketPrimesByLastDigit(Scene):
CONFIG = {
"bar_colors": [YELLOW, BLUE],
"mod": 10,
"max_n": 10000,
"n_to_animate": 20,
"n_to_show": 1000,
"x_label_scale_factor": 1,
"x_axis_label": "Last digit",
"bar_width": 0.5,
}
def construct(self):
self.add_axes()
self.add_bars()
self.bucket_primes()
def add_axes(self):
mod = self.mod
axes = Axes(
x_min=0,
x_max=mod + 0.5,
x_axis_config={
"unit_size": 10 / mod,
"include_tip": False,
},
y_min=0,
y_max=100,
y_axis_config={
"unit_size": 0.055,
"tick_frequency": 12.5,
"include_tip": False,
},
)
x_labels = VGroup()
for x in range(mod):
digit = Integer(x)
digit.scale(self.x_label_scale_factor)
digit.next_to(axes.x_axis.n2p(x + 1), DOWN, MED_SMALL_BUFF)
x_labels.add(digit)
self.modify_x_labels(x_labels)
x_labels.set_submobject_colors_by_gradient(*self.bar_colors)
axes.add(x_labels)
axes.x_labels = x_labels
y_labels = VGroup()
for y in range(25, 125, 25):
label = Integer(y, unit="\\%")
label.next_to(axes.y_axis.n2p(y), LEFT, MED_SMALL_BUFF)
y_labels.add(label)
axes.add(y_labels)
x_axis_label = TextMobject(self.x_axis_label)
x_axis_label.next_to(axes.x_axis.get_end(), RIGHT, buff=MED_LARGE_BUFF)
axes.add(x_axis_label)
y_axis_label = TextMobject("Proportion")
y_axis_label.next_to(axes.y_axis.get_end(), UP, buff=MED_LARGE_BUFF)
# y_axis_label.set_color(self.bar_colors[0])
axes.add(y_axis_label)
axes.center()
axes.set_width(FRAME_WIDTH - 1)
axes.to_edge(DOWN)
self.axes = axes
self.add(axes)
def add_bars(self):
axes = self.axes
mod = self.mod
count_trackers = Group(*[
ValueTracker(0)
for x in range(mod)
])
bars = VGroup()
for x in range(mod):
bar = Rectangle(
height=1,
width=self.bar_width,
fill_opacity=1,
)
bar.bottom = axes.x_axis.n2p(x + 1)
bars.add(bar)
bars.set_submobject_colors_by_gradient(*self.bar_colors)
bars.set_stroke(WHITE, 1)
def update_bars(bars):
values = [ct.get_value() for ct in count_trackers]
total = sum(values)
if total == 0:
props = [0 for x in range(mod)]
elif total < 1:
props = values
else:
props = [value / total for value in values]
for bar, prop in zip(bars, props):
bar.set_height(
max(
1e-5,
100 * prop * axes.y_axis.unit_size,
),
stretch=True
)
# bar.set_height(1)
bar.move_to(bar.bottom, DOWN)
bars.add_updater(update_bars)
self.add(count_trackers)
self.add(bars)
self.bars = bars
self.count_trackers = count_trackers
def bucket_primes(self):
bars = self.bars
count_trackers = self.count_trackers
max_n = self.max_n
n_to_animate = self.n_to_animate
n_to_show = self.n_to_show
mod = self.mod
primes = VGroup(*[
Integer(prime).scale(2).to_edge(UP, buff=LARGE_BUFF)
for prime in read_in_primes(max_n)
])
arrow = Arrow(ORIGIN, DOWN)
x_labels = self.axes.x_labels
rects = VGroup(*map(SurroundingRectangle, x_labels))
rects.set_color(RED)
self.play(FadeIn(primes[0]))
for i, p, np in zip(it.count(), primes[:n_to_show], primes[1:]):
d = int(p.get_value()) % mod
self.add(rects[d])
if i < n_to_animate:
self.play(
p.scale, 0.5,
p.move_to, bars[d].get_top(),
p.set_opacity, 0,
FadeIn(np),
count_trackers[d].increment_value, 1,
)
self.remove(p)
else:
arrow.next_to(bars[d], UP)
self.add(arrow)
self.add(p)
count_trackers[d].increment_value(1)
self.wait(0.1)
self.remove(p)
self.remove(rects[d])
#
def modify_x_labels(self, labels):
pass
class PhraseDirichletsTheoremFor10(TeacherStudentsScene):
def construct(self):
expression = TexMobject(
"\\lim_{x \\to \\infty}",
"\\left(",
"{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}",
"\\over",
"\\text{\\# of primes $p$ where $p \\le x$}}",
"\\right)",
"=",
"\\frac{1}{4}",
)
lim, lp, num, over, denom, rp, eq, fourth = expression
expression.shift(UP)
denom.save_state()
denom.move_to(self.hold_up_spot, DOWN)
denom.shift_onto_screen()
num[len(denom):].set_color(YELLOW)
x_example = VGroup(
TextMobject("Think, for example, $x = $"),
Integer(int(1e6)),
)
x_example.arrange(RIGHT)
x_example.scale(1.5)
x_example.to_edge(UP)
#
teacher = self.teacher
students = self.students
self.play(
FadeInFromDown(denom),
teacher.change, "raise_right_hand",
self.get_student_changes(*["pondering"] * 3),
)
self.wait()
self.play(FadeInFromDown(x_example))
self.wait()
self.play(
Restore(denom),
teacher.change, "thinking",
)
self.play(
TransformFromCopy(denom, num[:len(denom)]),
Write(over),
)
self.play(
Write(num[len(denom):]),
students[0].change, "confused",
students[2].change, "erm",
)
self.wait(2)
self.play(
Write(lp),
Write(rp),
Write(eq),
)
self.play(FadeInFrom(fourth, LEFT))
self.play(FadeInFrom(lim, RIGHT))
self.play(
ChangeDecimalToValue(
x_example[1], int(1e7),
run_time=8,
rate_func=linear,
),
VFadeOut(x_example, run_time=8),
self.get_student_changes(*["thinking"] * 3),
Blink(
teacher,
run_time=4,
rate_func=squish_rate_func(there_and_back, 0.65, 0.7)
),
)
class InsertNewResidueClasses(Scene):
def construct(self):
nums = VGroup(*map(Integer, [3, 7, 9]))
colors = [GREEN, TEAL, BLUE]
for num, color in zip(nums, colors):
num.set_color(color)
num.add_background_rectangle(buff=SMALL_BUFF, opacity=1)
self.play(FadeInFrom(num, UP))
self.wait()
class BucketPrimesBy44(BucketPrimesByLastDigit):
CONFIG = {
"mod": 44,
"n_to_animate": 5,
"x_label_scale_factor": 0.5,
"x_axis_label": "r mod 44",
"bar_width": 0.1,
}
def modify_x_labels(self, labels):
labels[::2].set_opacity(0)
class BucketPrimesBy9(BucketPrimesByLastDigit):
CONFIG = {
"mod": 9,
"n_to_animate": 5,
"x_label_scale_factor": 1,
"x_axis_label": "r mod 9",
"bar_width": 1,
}
def modify_x_labels(self, labels):
pass
class DirichletIn1837(MovingCameraScene):
def construct(self):
# Add timeline
dates = list(range(1780, 2030, 10))
timeline = NumberLine(
x_min=1700,
x_max=2020,
tick_frequency=1,
numbers_with_elongated_ticks=dates,
unit_size=0.2,
stroke_color=GREY,
stroke_width=2,
)
timeline.add_numbers(
*dates,
number_config={"group_with_commas": False},
)
timeline.numbers.shift(SMALL_BUFF * DOWN)
timeline.to_edge(RIGHT)
# Special dates
d_arrow, rh_arrow, pnt_arrow = arrows = VGroup(*[
Vector(DOWN).next_to(timeline.n2p(date), UP)
for date in [1837, 1859, 1896]
])
d_label, rh_label, pnt_label = labels = VGroup(*[
TextMobject(text).next_to(arrow, UP)
for arrow, text in zip(arrows, [
"Dirichlet's\\\\theorem\\\\1837",
"Riemann\\\\hypothesis\\\\1859",
"Prime number\\\\theorem\\\\1896",
])
])
# Back in time
frame = self.camera_frame
self.add(timeline, arrows, labels)
self.play(
frame.move_to, timeline.n2p(1837),
run_time=4,
)
self.wait()
# Show picture
image = ImageMobject("Dirichlet")
image.set_height(3)
image.next_to(d_label, LEFT)
self.play(FadeInFrom(image, RIGHT))
self.wait()
# Flash
self.play(
Flash(
d_label.get_center(),
num_lines=12,
line_length=0.25,
flash_radius=1.5,
line_stroke_width=3,
lag_ratio=0.05,
)
)
self.wait()
# Transition title
title, underline = self.get_title_and_underline()
n = len(title[0])
self.play(
ReplacementTransform(d_label[0][:n], title[0][:n]),
FadeOut(d_label[0][n:]),
LaggedStartMap(
FadeOutAndShiftDown, Group(
image, d_arrow,
rh_label, rh_arrow,
pnt_label, pnt_arrow,
*timeline,
)
),
)
self.play(ShowCreation(underline))
self.wait()
def get_title_and_underline(self):
frame = self.camera_frame
title = TextMobject("Dirichlet's theorem")
title.scale(1.5)
title.next_to(frame.get_top(), DOWN, buff=MED_LARGE_BUFF)
underline = Line()
underline.match_width(title)
underline.next_to(title, DOWN, SMALL_BUFF)
return title, underline
class PhraseDirichletsTheorem(DirichletIn1837):
def construct(self):
self.add(*self.get_title_and_underline())
# Copy-pasted, which isn't great
expression = TexMobject(
"\\lim_{x \\to \\infty}",
"\\left(",
"{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}",
"\\over",
"\\text{\\# of primes $p$ where $p \\le x$}}",
"\\right)",
"=",
"\\frac{1}{4}",
)
lim, lp, num, over, denom, rp, eq, fourth = expression
expression.shift(1.5 * UP)
expression.to_edge(LEFT, MED_SMALL_BUFF)
num[len(denom):].set_color(YELLOW)
#
# Terms and labels
ten = num[-2:]
one = num[-6]
four = fourth[-1]
N = TexMobject("N")
r = TexMobject("r")
one_over_phi_N = TexMobject("1", "\\over", "\\phi(", "N", ")")
N.set_color(MAROON_B)
r.set_color(BLUE)
one_over_phi_N.set_color_by_tex("N", N.get_color())
N.move_to(ten, DL)
r.move_to(one, DOWN)
one_over_phi_N.move_to(fourth, LEFT)
N_label = TextMobject("$N$", " is any number")
N_label.set_color_by_tex("N", N.get_color())
N_label.next_to(expression, DOWN, LARGE_BUFF)
r_label = TextMobject("$r$", " is coprime to ", "$N$")
r_label[0].set_color(r.get_color())
r_label[2].set_color(N.get_color())
r_label.next_to(N_label, DOWN, MED_LARGE_BUFF)
phi_N_label = TexMobject(
"\\phi({10}) = ",
"\\#\\{1, 3, 7, 9\\} = 4",
tex_to_color_map={
"{10}": N.get_color(),
}
)
phi_N_label[-1][2:9:2].set_color(r.get_color())
phi_N_label.next_to(r_label, DOWN, MED_LARGE_BUFF)
#
self.play(
LaggedStart(*[
FadeIn(denom),
ShowCreation(over),
FadeIn(num),
Write(VGroup(lp, rp)),
FadeIn(lim),
Write(VGroup(eq, fourth)),
]),
run_time=3,
lag_ratio=0.7,
)
self.wait()
for mob in denom, num:
self.play(ShowCreationThenFadeAround(mob))
self.wait()
self.play(
FadeInFrom(r, DOWN),
FadeOutAndShift(one, UP),
)
self.play(
FadeInFrom(N, DOWN),
FadeOutAndShift(ten, UP),
)
self.wait()
self.play(
TransformFromCopy(N, N_label[0]),
FadeIn(N_label[1:], DOWN)
)
self.wait()
self.play(
FadeIn(r_label[1:-1], DOWN),
TransformFromCopy(r, r_label[0]),
)
self.play(
TransformFromCopy(N_label[0], r_label[-1]),
)
self.wait()
self.play(
ShowCreationThenFadeAround(fourth),
)
self.play(
FadeInFrom(one_over_phi_N[2:], LEFT),
FadeOutAndShift(four, RIGHT),
ReplacementTransform(fourth[0], one_over_phi_N[0][0]),
ReplacementTransform(fourth[1], one_over_phi_N[1][0]),
)
self.play(
FadeInFrom(phi_N_label, DOWN)
)
self.wait()
# Fancier version
new_expression = TexMobject(
"\\lim_{x \\to \\infty}",
"\\left(",
"{\\pi(x; {N}, {r})",
"\\over",
"\\pi(x)}",
"\\right)",
"=",
"\\frac{1}{\\phi({N})}",
tex_to_color_map={
"{N}": N.get_color(),
"{r}": r.get_color(),
"\\pi": WHITE,
}
)
pis = new_expression.get_parts_by_tex("\\pi")
randy = Randolph(height=2)
randy.next_to(new_expression, LEFT, buff=LARGE_BUFF)
randy.shift(0.75 * DOWN)
new_expression.next_to(expression, DOWN, LARGE_BUFF)
ne_rect = SurroundingRectangle(new_expression, color=BLUE)
label_group = VGroup(N_label, r_label)
label_group.generate_target()
label_group.target.arrange(RIGHT, buff=LARGE_BUFF)
label_group.target.next_to(new_expression, DOWN, buff=LARGE_BUFF)
self.play(
FadeIn(randy),
)
self.play(
randy.change, "hooray", expression
)
self.play(Blink(randy))
self.wait()
self.play(
FadeIn(new_expression),
MoveToTarget(label_group),
phi_N_label.to_edge, DOWN, MED_LARGE_BUFF,
randy.change, "horrified", new_expression,
)
self.play(ShowCreation(ne_rect))
self.play(randy.change, "confused")
self.play(Blink(randy))
self.wait()
self.play(
LaggedStartMap(
ShowCreationThenFadeAround, pis,
),
randy.change, "angry", new_expression
)
self.wait()
self.play(Blink(randy))
self.wait()
class MoreModestDirichlet(Scene):
def construct(self):
ed = TextMobject(
"Each (coprime) residue class ",
"is equally dense with ",
"primes."
)
inf = TextMobject(
"Each (coprime) residue class ",
"has infinitely many ",
"primes."
)
ed[1].set_color(BLUE)
inf[1].set_color(GREEN)
for mob in [*ed, *inf]:
mob.save_state()
cross = Cross(ed[1])
c_group = VGroup(ed[1], cross)
self.add(ed)
self.wait()
self.play(ShowCreation(cross))
self.play(
c_group.shift, DOWN,
c_group.set_opacity, 0.5,
ReplacementTransform(ed[::2], inf[::2]),
FadeIn(inf[1])
)
self.wait()
self.remove(*inf)
self.play(
inf[1].shift, UP,
Restore(ed[0]),
Restore(ed[1]),
Restore(ed[2]),
FadeOut(cross),
)
self.wait()
class TalkAboutProof(TeacherStudentsScene):
def construct(self):
teacher = self.teacher
students = self.students
# Ask question
self.student_says(
"So how'd he\\\\prove it?",
student_index=0,
)
bubble = students[0].bubble
students[0].bubble = None
self.play(
teacher.change, "hesitant",
students[1].change, "happy",
students[2].change, "happy",
)
self.wait()
self.teacher_says(
"...er...it's a\\\\bit complicated",
target_mode="guilty",
)
self.change_all_student_modes(
"tired",
look_at_arg=teacher.bubble,
lag_ratio=0.1,
)
self.play(
FadeOut(bubble),
FadeOut(bubble.content),
)
self.wait(3)
# Bring up complex analysis
ca = TextMobject("Complex ", "Analysis")
ca.move_to(self.hold_up_spot, DOWN)
self.play(
teacher.change, "raise_right_hand",
FadeInFromDown(ca),
FadeOut(teacher.bubble),
FadeOut(teacher.bubble.content),
self.get_student_changes(*["pondering"] * 3),
)
self.wait()
self.play(
ca.scale, 2,
ca.center,
ca.to_edge, UP,
teacher.change, "happy",
)
self.play(ca[1].set_color, GREEN)
self.wait(2)
self.play(ca[0].set_color, YELLOW)
self.wait(2)
self.change_all_student_modes(
"confused", look_at_arg=ca,
)
self.wait(4)
class HighlightTwinPrimes(Scene):
def construct(self):
self.add_paper_titles()
self.show_twin_primes()
def add_paper_titles(self):
gpy = TextMobject(
"Goldston, Pintz, Yildirim\\\\",
"2005",
)
zhang = TextMobject("Zhang\\\\2014")
gpy.move_to(FRAME_WIDTH * LEFT / 4)
gpy.to_edge(UP)
zhang.move_to(FRAME_WIDTH * RIGHT / 4)
zhang.to_edge(UP)
self.play(LaggedStartMap(
FadeInFromDown, VGroup(gpy, zhang),
lag_ratio=0.3,
))
def show_twin_primes(self):
max_x = 300
line = NumberLine(
x_min=0,
x_max=max_x,
unit_size=0.5,
numbers_with_elongated_ticks=range(10, max_x, 10),
)
line.move_to(2.5 * DOWN + 7 * LEFT, LEFT)
line.add_numbers(*range(10, max_x, 10))
primes = read_in_primes(max_x)
prime_mobs = VGroup(*[
Integer(p).next_to(line.n2p(p), UP)
for p in primes
])
dots = VGroup(*[Dot(line.n2p(p)) for p in primes])
arcs = VGroup()
for pm, npm in zip(prime_mobs, prime_mobs[1:]):
p = pm.get_value()
np = npm.get_value()
if np - p == 2:
angle = 30 * DEGREES
arc = Arc(
start_angle=angle,
angle=PI - 2 * angle,
color=RED,
)
arc.set_width(
get_norm(npm.get_center() - pm.get_center())
)
arc.next_to(VGroup(pm, npm), UP, SMALL_BUFF)
arcs.add(arc)
dots.set_color(TEAL)
prime_mobs.set_color(TEAL)
line.add(dots)
self.play(
FadeIn(line, lag_ratio=0.9),
LaggedStartMap(FadeInFromDown, prime_mobs),
run_time=2,
)
line.add(prime_mobs)
self.wait()
self.play(FadeIn(arcs))
self.play(
line.shift, 100 * LEFT,
arcs.shift, 100 * LEFT,
run_time=20,
rate_func=lambda t: smooth(t, 5)
)
self.wait()
class RandomToImportant(PiCreatureScene):
CONFIG = {
"default_pi_creature_kwargs": {
"color": GREY_BROWN,
},
"camera_config": {
"background_color": DARKER_GREY,
}
}
def construct(self):
morty = self.pi_creature
morty.center().to_edge(DOWN)
left_comment = TextMobject("Arbitrary question")
left_comment.to_edge(UP)
left_comment.shift(3.5 * LEFT)
right_comment = TextMobject("Deep fact")
right_comment.to_edge(UP)
right_comment.shift(3.5 * RIGHT)
arrow = Arrow(
left_comment.get_right(),
right_comment.get_left(),
buff=0.5,
)
self.play(
morty.change, "raise_left_hand", left_comment,
FadeInFromDown(left_comment)
)
self.wait(2)
self.play(
morty.change, "raise_right_hand", right_comment,
FadeInFromDown(right_comment)
)
self.play(
ShowCreation(arrow),
morty.look_at, right_comment,
)
self.wait(2)
class RandomWalkOfTopics(Scene):
CONFIG = {
"n_dots": 30,
"n_edge_range": [2, 4],
"super_dot_n_edges": 20,
"isolated_threashold": 0.5,
}
def construct(self):
self.setup_network()
self.define_important()
self.perform_walk()
def setup_network(self):
n_dots = self.n_dots
dots = VGroup()
while len(dots) < n_dots:
point = np.random.uniform(-1, 1, size=3)
point[2] = 0
point[0] *= 7
point[1] *= 3
isolated = True
for dot in dots:
if get_norm(dot.get_center() - point) < self.isolated_threashold:
isolated = False
if not isolated:
continue
dot = Dot(point)
dot.edges = VGroup()
dot.neighbors = VGroup()
dots.add(dot)
super_dot = dots[len(dots) // 2]
all_edges = VGroup()
def add_edge(d1, d2):
if d1 is d2:
return
edge = Line(
d1.get_center(), d2.get_center(),
buff=d1.get_width() / 2
)
d1.edges.add(edge)
d2.edges.add(edge)
d1.neighbors.add(d2)
d2.neighbors.add(d1)
all_edges.add(edge)
for dot in dots:
# others = list(dots[i + 1:])
others = [d for d in dots if d is not dot]
others.sort(key=lambda d: get_norm(d.get_center() - dot.get_center()))
n_edges = np.random.randint(*self.n_edge_range)
for other in others[:n_edges]:
if dot in other.neighbors:
continue
add_edge(dot, other)
for dot in dots:
if len(super_dot.neighbors) > self.super_dot_n_edges:
break
elif dot in super_dot.neighbors:
continue
add_edge(super_dot, dot)
dots.sort(lambda p: p[0])
all_edges.set_stroke(WHITE, 2)
dots.set_fill(LIGHT_GREY, 1)
VGroup(dots, all_edges).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.dots = dots
self.edges = all_edges
self.super_dot = super_dot
def define_important(self):
sd = self.super_dot
dots = self.dots
edges = self.edges
sd.set_color(RED)
for mob in [*dots, *edges]:
mob.save_state()
mob.set_opacity(0)
sd.set_opacity(1)
sd.edges.set_opacity(1)
sd.neighbors.set_opacity(1)
# angles = np.arange(0, TAU, TAU / len(sd.neighbors))
# center = 0.5 * DOWN
# sd.move_to(center)
# for dot, edge, angle in zip(sd.neighbors, sd.edges, angles):
# dot.move_to(center + rotate_vector(2.5 * RIGHT, angle))
# if edge.get_length() > 0:
# edge.put_start_and_end_on(
# sd.get_center(),
# dot.get_center()
# )
# rad = dot.get_width() / 2
# llen = edge.get_length()
# edge.scale((llen - 2 * rad) / llen)
title = VGroup(
TextMobject("Important"),
TexMobject("\\Leftrightarrow"),
TextMobject("Many connections"),
)
title.scale(1.5)
title.arrange(RIGHT, buff=MED_LARGE_BUFF)
title.to_edge(UP)
arrow_words = TextMobject("(in my view)")
arrow_words.set_width(2 * title[1].get_width())
arrow_words.next_to(title[1], UP, SMALL_BUFF)
title[0].save_state()
title[0].set_x(0)
self.add(title[0])
self.play(
FadeInFromLarge(sd),
title[0].set_color, RED,
)
title[0].saved_state.set_color(RED)
self.play(
Restore(title[0]),
GrowFromCenter(title[1]),
FadeIn(arrow_words),
FadeInFrom(title[2], LEFT),
LaggedStartMap(
ShowCreation, sd.edges,
run_time=3,
),
LaggedStartMap(
GrowFromPoint, sd.neighbors,
lambda m: (m, sd.get_center()),
run_time=3,
),
)
self.wait()
self.play(*map(Restore, [*dots, *edges]))
self.wait()
def perform_walk(self):
# dots = self.dots
# edges = self.edges
sd = self.super_dot
path = VGroup(sd)
random.seed(1)
for x in range(3):
new_choice = None
while new_choice is None or new_choice in path:
new_choice = random.choice(path[0].neighbors)
path.add_to_back(new_choice)
for d1, d2 in zip(path, path[1:]):
self.play(Flash(d1.get_center()))
self.play(
ShowCreationThenDestruction(
Line(
d1.get_center(),
d2.get_center(),
color=YELLOW,
)
)
)
self.play(Flash(sd))
self.play(LaggedStart(*[
ApplyMethod(
edge.set_stroke, YELLOW, 5,
rate_func=there_and_back,
)
for edge in sd.edges
]))
self.wait()
class DeadEnds(RandomWalkOfTopics):
CONFIG = {
"n_dots": 20,
"n_edge_range": [2, 4],
"super_dot_n_edges": 2,
"random_seed": 1,
}
def construct(self):
self.setup_network()
dots = self.dots
edges = self.edges
self.add(dots, edges)
VGroup(
edges[3],
edges[4],
edges[7],
edges[10],
edges[12],
edges[15],
edges[27],
edges[30],
edges[33],
).set_opacity(0)
# for i, edge in enumerate(edges):
# self.add(Integer(i).move_to(edge))
class Rediscovery(Scene):
def construct(self):
randy = Randolph()
randy.to_edge(DOWN)
randy.shift(2 * RIGHT)
lightbulb = Lightbulb()
lightbulb.set_stroke(width=4)
lightbulb.scale(1.5)
lightbulb.next_to(randy, UP)
rings = self.get_rings(
lightbulb.get_center(),
max_radius=10.0,
delta_r=0.1,
)
bubble = ThoughtBubble()
bubble.pin_to(randy)
# bubble[-1].set_fill(GREEN_SCREEN, 0.5)
self.play(
randy.change, "pondering",
ShowCreation(bubble),
FadeInFromDown(lightbulb),
)
self.add(rings, bubble)
self.play(
randy.change, "thinking",
LaggedStartMap(
VFadeInThenOut,
rings,
lag_ratio=0.002,
run_time=3,
)
)
self.wait(4)
def get_rings(self, center, max_radius, delta_r):
radii = np.arange(0, max_radius, delta_r)
rings = VGroup(*[
Annulus(
inner_radius=r1,
outer_radius=r2,
fill_opacity=0.75 * (1 - fdiv(r1, max_radius)),
fill_color=YELLOW
)
for r1, r2 in zip(radii, radii[1:])
])
rings.move_to(center)
return rings
class BePlayful(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"So be playful!",
target_mode="hooray",
)
self.change_student_modes("thinking", "hooray", "happy")
self.wait(3)
class SpiralsPatronThanks(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
"Juan Benet",
"Kurt Dicus",
"Vassili Philippov",
"Burt Humburg",
"Matt Russell",
"Scott Gray",
"soekul",
"Tihan Seale",
"D. Sivakumar",
"Ali Yahya",
"Arthur Zey",
"dave nicponski",
"Joseph Kelly",
"Kaustuv DeBiswas",
"kkm",
"Lambda AI Hardware",
"Lukas Biewald",
"Mark Heising",
"Nicholas Cahill",
"Peter Mcinerney",
"Quantopian",
"Scott Walter, Ph.D.",
"Tauba Auerbach",
"Yana Chernobilsky",
"Yu Jun",
"Jordan Scales",
"Lukas -krtek.net- Novy",
"Andrew Weir",
"Britt Selvitelle",
"Britton Finley",
"David Gow",
"J",
"Jonathan Wilson",
"Joseph John Cox",
"Magnus Dahlström",
"Mattéo Delabre",
"Randy C. Will",
"Ryan Atallah",
"Luc Ritchie",
"1stViewMaths",
"Aidan Shenkman",
"Alex Mijalis",
"Alexis Olson",
"Andreas Benjamin Brössel",
"Andrew Busey",
"Andrew R. Whalley",
"Ankalagon",
"Anthony Turvey",
"Antoine Bruguier",
"Antonio Juarez",
"Arjun Chakroborty",
"Art Ianuzzi",
"Austin Goodman",
"Avi Finkel",
"Awoo",
"Azeem Ansar",
"AZsorcerer",
"Barry Fam",
"Bernd Sing",
"Boris Veselinovich",
"Bradley Pirtle",
"Brian Staroselsky",
"Calvin Lin",
"Charles Southerland",
"Charlie N",
"Chris Connett",
"Christian Kaiser",
"Clark Gaebel",
"Danger Dai",
"Daniel Herrera C",
"Daniel Pang",
"Dave B",
"Dave Kester",
"David B. Hill",
"David Clark",
"DeathByShrimp",
"Delton Ding",
"Dominik Wagner",
"eaglle",
"emptymachine",
"Eric Younge",
"Ero Carrera",
"Eryq Ouithaqueue",
"Federico Lebron",
"Fernando Via Canel",
"Frank R. Brown, Jr.",
"Giovanni Filippi",
"Hal Hildebrand",
"Hause Lin",
"Hitoshi Yamauchi",
"Ivan Sorokin",
"j eduardo perez",
"Jacob Baxter",
"Jacob Harmon",
"Jacob Hartmann",
"Jacob Magnuson",
"Jameel Syed",
"James Stevenson",
"Jason Hise",
"Jeff Linse",
"Jeff Straathof",
"John C. Vesey",
"John Griffith",
"John Haley",
"John V Wertheim",
"Jonathan Eppele",
"Josh Kinnear",
"Joshua Claeys",
"Kai-Siang Ang",
"Kanan Gill",
"Kartik Cating-Subramanian",
"L0j1k",
"Lee Redden",
"Linh Tran",
"Ludwig Schubert",
"Magister Mugit",
"Mark B Bahu",
"Mark Mann",
"Martin Price",
"Mathias Jansson",
"Matt Langford",
"Matt Roveto",
"Matthew Bouchard",
"Matthew Cocke",
"Michael Faust",
"Michael Hardel",
"Michele Donadoni",
"Mirik Gogri",
"Mustafa Mahdi",
"Márton Vaitkus",
"Nero Li",
"Nikita Lesnikov",
"Omar Zrien",
"Owen Campbell-Moore",
"Patrick Lucas",
"Pedro Igor Salomão Budib",
"Peter Ehrnstrom",
"RedAgent14",
"rehmi post",
"Rex Godby",
"Richard Barthel",
"Ripta Pasay",
"Rish Kundalia",
"Roman Sergeychik",
"Roobie",
"Ryan Williams",
"Sebastian Garcia",
"Solara570",
"Steven Siddals",
"Stevie Metke",
"Suthen Thomas",
"Tal Einav",
"Ted Suzman",
"The Responsible One",
"Thomas Tarler",
"Tianyu Ge",
"Tom Fleming",
"Tyler VanValkenburg",
"Valeriy Skobelev",
"Veritasium",
"Vinicius Reis",
"Xuanji Li",
"Yavor Ivanov",
"YinYangBalance.Asia",
]
}
class Thumbnail(SpiralScene):
CONFIG = {
"max_N": 8000,
"just_show": True,
}
def construct(self):
self.add_dots()
if not self.just_show:
pass
def add_dots(self):
self.set_scale(scale=1e3)
p_spiral = self.get_prime_p_spiral(self.max_N)
dots = VGroup(*[
Dot(
point,
radius=interpolate(0.01, 0.07, min(0.5 * get_norm(point), 1)),
fill_color=TEAL,
# fill_opacity=interpolate(0.5, 1, min(get_norm(point), 1))
)
for point in p_spiral.points
])
dots.set_fill([TEAL_E, TEAL_A])
dots.set_stroke(BLACK, 1)
label = TextMobject(
"($p$, $p$) for all primes $p$,\\\\",
"in polar coordinates",
tex_to_color_map={
"$p$": YELLOW,
},
)
label.scale(2)
label.set_stroke(BLACK, 10, background=True)
label.add_background_rectangle_to_submobjects()
label.to_corner(DL, MED_LARGE_BUFF)
self.add(dots)
self.add(label)
self.dots = dots