mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
713 lines
19 KiB
Python
713 lines
19 KiB
Python
from manimlib.imports import *
|
|
|
|
|
|
class GeneralizeToComplexFunctions(Scene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"x_min": 0,
|
|
"x_max": 10,
|
|
"x_axis_config": {
|
|
"stroke_width": 2,
|
|
},
|
|
"y_min": -2.5,
|
|
"y_max": 2.5,
|
|
"y_axis_config": {
|
|
"tick_frequency": 0.25,
|
|
"unit_size": 1.5,
|
|
"include_tip": False,
|
|
"stroke_width": 2,
|
|
},
|
|
},
|
|
"complex_plane_config": {
|
|
"axis_config": {
|
|
"unit_size": 2
|
|
}
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.show_cosine_wave()
|
|
self.transition_to_complex_plane()
|
|
self.add_rotating_vectors_making_cos()
|
|
|
|
def show_cosine_wave(self):
|
|
axes = Axes(**self.axes_config)
|
|
axes.shift(2 * LEFT - axes.c2p(0, 0))
|
|
y_axis = axes.y_axis
|
|
y_labels = y_axis.get_number_mobjects(
|
|
*range(-2, 3),
|
|
number_config={"num_decimal_places": 1},
|
|
)
|
|
|
|
t_tracker = ValueTracker(0)
|
|
t_tracker.add_updater(lambda t, dt: t.increment_value(dt))
|
|
get_t = t_tracker.get_value
|
|
|
|
def func(x):
|
|
return 2 * np.cos(x)
|
|
|
|
cos_x_max = 20
|
|
cos_wave = axes.get_graph(func, x_max=cos_x_max)
|
|
cos_wave.set_color(YELLOW)
|
|
shown_cos_wave = cos_wave.copy()
|
|
shown_cos_wave.add_updater(
|
|
lambda m: m.pointwise_become_partial(
|
|
cos_wave, 0,
|
|
np.clip(get_t() / cos_x_max, 0, 1),
|
|
),
|
|
)
|
|
|
|
dot = Dot()
|
|
dot.set_color(PINK)
|
|
dot.add_updater(lambda d: d.move_to(
|
|
y_axis.n2p(func(get_t())),
|
|
))
|
|
|
|
h_line = always_redraw(lambda: Line(
|
|
dot.get_right(),
|
|
shown_cos_wave.get_end(),
|
|
stroke_width=1,
|
|
))
|
|
|
|
real_words = TextMobject(
|
|
"Real number\\\\output"
|
|
)
|
|
real_words.to_edge(LEFT)
|
|
real_words.shift(2 * UP)
|
|
real_arrow = Arrow()
|
|
real_arrow.add_updater(
|
|
lambda m: m.put_start_and_end_on(
|
|
real_words.get_corner(DR),
|
|
dot.get_center(),
|
|
).scale(0.9),
|
|
)
|
|
|
|
self.add(t_tracker)
|
|
self.add(axes)
|
|
self.add(y_labels)
|
|
self.add(shown_cos_wave)
|
|
self.add(dot)
|
|
self.add(h_line)
|
|
|
|
self.wait(2)
|
|
self.play(
|
|
FadeInFrom(real_words, RIGHT),
|
|
FadeIn(real_arrow),
|
|
)
|
|
self.wait(5)
|
|
|
|
y_axis.generate_target()
|
|
y_axis.target.rotate(-90 * DEGREES)
|
|
y_axis.target.center()
|
|
y_axis.target.scale(2 / 1.5)
|
|
y_labels.generate_target()
|
|
for label in y_labels.target:
|
|
label.next_to(
|
|
y_axis.target.n2p(label.get_value()),
|
|
DOWN, MED_SMALL_BUFF,
|
|
)
|
|
self.play(
|
|
FadeOut(shown_cos_wave),
|
|
FadeOut(axes.x_axis),
|
|
FadeOut(h_line),
|
|
)
|
|
self.play(
|
|
MoveToTarget(y_axis),
|
|
MoveToTarget(y_labels),
|
|
real_words.shift, 2 * RIGHT + UP,
|
|
)
|
|
self.wait()
|
|
|
|
self.y_axis = y_axis
|
|
self.y_labels = y_labels
|
|
self.real_words = real_words
|
|
self.real_arrow = real_arrow
|
|
self.dot = dot
|
|
self.t_tracker = t_tracker
|
|
|
|
def transition_to_complex_plane(self):
|
|
y_axis = self.y_axis
|
|
y_labels = self.y_labels
|
|
|
|
plane = self.get_complex_plane()
|
|
plane_words = plane.label
|
|
|
|
self.add(plane, *self.get_mobjects())
|
|
self.play(
|
|
FadeOut(y_labels),
|
|
FadeOut(y_axis),
|
|
ShowCreation(plane),
|
|
)
|
|
self.play(Write(plane_words))
|
|
self.wait()
|
|
|
|
self.plane = plane
|
|
self.plane_words = plane_words
|
|
|
|
def add_rotating_vectors_making_cos(self):
|
|
plane = self.plane
|
|
real_words = self.real_words
|
|
real_arrow = self.real_arrow
|
|
t_tracker = self.t_tracker
|
|
get_t = t_tracker.get_value
|
|
|
|
v1 = Vector(2 * RIGHT)
|
|
v2 = Vector(2 * RIGHT)
|
|
v1.set_color(BLUE)
|
|
v2.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5))
|
|
v1.add_updater(
|
|
lambda v: v.set_angle(get_t())
|
|
)
|
|
v2.add_updater(
|
|
lambda v: v.set_angle(-get_t())
|
|
)
|
|
v1.add_updater(
|
|
lambda v: v.shift(plane.n2p(0) - v.get_start())
|
|
)
|
|
# Change?
|
|
v2.add_updater(
|
|
lambda v: v.shift(plane.n2p(0) - v.get_start())
|
|
)
|
|
|
|
ghost_v1 = v1.copy()
|
|
ghost_v1.set_opacity(0.5)
|
|
ghost_v1.add_updater(
|
|
lambda v: v.shift(
|
|
v2.get_end() - v.get_start()
|
|
)
|
|
)
|
|
|
|
ghost_v2 = v2.copy()
|
|
ghost_v2.set_opacity(0.5)
|
|
ghost_v2.add_updater(
|
|
lambda v: v.shift(
|
|
v1.get_end() - v.get_start()
|
|
)
|
|
)
|
|
|
|
circle = Circle(color=GREY_BROWN)
|
|
circle.set_stroke(width=1)
|
|
circle.set_width(2 * v1.get_length())
|
|
circle.move_to(plane.n2p(0))
|
|
|
|
formula = TexMobject(
|
|
# "\\cos(x) ="
|
|
# "{1 \\over 2}e^{ix} +"
|
|
# "{1 \\over 2}e^{-ix}",
|
|
"2\\cos(x) =",
|
|
"e^{ix}", "+", "e^{-ix}",
|
|
tex_to_color_map={
|
|
"e^{ix}": v1.get_color(),
|
|
"e^{-ix}": v2.get_color(),
|
|
}
|
|
)
|
|
formula.next_to(ORIGIN, UP, buff=0.75)
|
|
# formula.add_background_rectangle()
|
|
formula.set_stroke(BLACK, 3, background=True)
|
|
formula.to_edge(LEFT, buff=MED_SMALL_BUFF)
|
|
formula_brace = Brace(formula[1:], UP)
|
|
formula_words = formula_brace.get_text(
|
|
"Sum of\\\\rotations"
|
|
)
|
|
formula_words.set_stroke(BLACK, 3, background=True)
|
|
|
|
randy = Randolph()
|
|
randy.to_corner(DL)
|
|
randy.look_at(formula)
|
|
|
|
self.play(
|
|
FadeOut(real_words),
|
|
FadeOut(real_arrow),
|
|
)
|
|
self.play(
|
|
FadeIn(v1),
|
|
FadeIn(v2),
|
|
FadeIn(circle),
|
|
FadeIn(ghost_v1),
|
|
FadeIn(ghost_v2),
|
|
)
|
|
self.wait(3)
|
|
self.play(FadeInFromDown(formula))
|
|
self.play(
|
|
GrowFromCenter(formula_brace),
|
|
FadeIn(formula_words),
|
|
)
|
|
self.wait(2)
|
|
self.play(FadeIn(randy))
|
|
self.play(randy.change, "pleading")
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(randy.change, "confused")
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(FadeOut(randy))
|
|
self.wait(20)
|
|
|
|
#
|
|
def get_complex_plane(self):
|
|
plane = ComplexPlane(**self.complex_plane_config)
|
|
plane.add_coordinates()
|
|
|
|
plane.label = TextMobject("Complex plane")
|
|
plane.label.scale(1.5)
|
|
plane.label.to_corner(UR, buff=MED_SMALL_BUFF)
|
|
return plane
|
|
|
|
|
|
class ClarifyInputAndOutput(GeneralizeToComplexFunctions):
|
|
CONFIG = {
|
|
"input_space_rect_config": {
|
|
"stroke_color": WHITE,
|
|
"stroke_width": 1,
|
|
"fill_color": DARKER_GREY,
|
|
"fill_opacity": 1,
|
|
"width": 6,
|
|
"height": 2,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_plane()
|
|
self.setup_input_space()
|
|
self.setup_input_trackers()
|
|
|
|
self.describe_input()
|
|
self.describe_output()
|
|
|
|
def setup_plane(self):
|
|
plane = self.get_complex_plane()
|
|
plane.sublabel = TextMobject("(Output space)")
|
|
plane.sublabel.add_background_rectangle()
|
|
plane.sublabel.next_to(plane.label, DOWN)
|
|
self.add(plane, plane.label)
|
|
self.plane = plane
|
|
|
|
def setup_input_space(self):
|
|
rect = Rectangle(**self.input_space_rect_config)
|
|
rect.to_corner(UL, buff=SMALL_BUFF)
|
|
|
|
input_line = self.get_input_line(rect)
|
|
input_words = TextMobject("Input space")
|
|
input_words.next_to(
|
|
rect.get_bottom(), UP,
|
|
SMALL_BUFF,
|
|
)
|
|
|
|
self.add(rect)
|
|
self.add(input_line)
|
|
|
|
self.input_rect = rect
|
|
self.input_line = input_line
|
|
self.input_words = input_words
|
|
|
|
def setup_input_trackers(self):
|
|
plane = self.plane
|
|
input_line = self.input_line
|
|
input_tracker = ValueTracker(0)
|
|
get_input = input_tracker.get_value
|
|
|
|
input_dot = Dot()
|
|
input_dot.set_color(PINK)
|
|
f_always(
|
|
input_dot.move_to,
|
|
lambda: input_line.n2p(get_input())
|
|
)
|
|
|
|
input_decimal = DecimalNumber()
|
|
input_decimal.scale(0.7)
|
|
always(input_decimal.next_to, input_dot, UP)
|
|
f_always(input_decimal.set_value, get_input)
|
|
|
|
path = self.get_path()
|
|
|
|
def get_output_point():
|
|
return path.point_from_proportion(
|
|
get_input()
|
|
)
|
|
|
|
output_dot = Dot()
|
|
output_dot.match_style(input_dot)
|
|
f_always(output_dot.move_to, get_output_point)
|
|
|
|
output_vector = Vector()
|
|
output_vector.set_color(WHITE)
|
|
output_vector.add_updater(
|
|
lambda v: v.put_start_and_end_on(
|
|
plane.n2p(0),
|
|
get_output_point()
|
|
)
|
|
)
|
|
|
|
output_decimal = DecimalNumber()
|
|
output_decimal.scale(0.7)
|
|
always(output_decimal.next_to, output_dot, UR, SMALL_BUFF)
|
|
f_always(
|
|
output_decimal.set_value,
|
|
lambda: plane.p2n(get_output_point()),
|
|
)
|
|
|
|
self.input_tracker = input_tracker
|
|
self.input_dot = input_dot
|
|
self.input_decimal = input_decimal
|
|
self.path = path
|
|
self.output_dot = output_dot
|
|
self.output_vector = output_vector
|
|
self.output_decimal = output_decimal
|
|
|
|
def describe_input(self):
|
|
input_tracker = self.input_tracker
|
|
|
|
self.play(FadeInFrom(self.input_words, UP))
|
|
self.play(
|
|
FadeInFromLarge(self.input_dot),
|
|
FadeIn(self.input_decimal),
|
|
)
|
|
for value in 1, 0:
|
|
self.play(
|
|
input_tracker.set_value, value,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
|
|
def describe_output(self):
|
|
path = self.path
|
|
output_dot = self.output_dot
|
|
output_decimal = self.output_decimal
|
|
input_dot = self.input_dot
|
|
input_tracker = self.input_tracker
|
|
plane = self.plane
|
|
real_line = plane.x_axis.copy()
|
|
real_line.set_stroke(RED, 4)
|
|
real_words = TextMobject("Real number line")
|
|
real_words.next_to(ORIGIN, UP)
|
|
real_words.to_edge(RIGHT)
|
|
|
|
traced_path = TracedPath(output_dot.get_center)
|
|
traced_path.match_style(path)
|
|
|
|
self.play(
|
|
ShowCreation(real_line),
|
|
FadeInFrom(real_words, DOWN)
|
|
)
|
|
self.play(
|
|
FadeOut(real_line),
|
|
FadeOut(real_words),
|
|
)
|
|
self.play(
|
|
FadeInFrom(plane.sublabel, UP)
|
|
)
|
|
self.play(
|
|
FadeIn(output_decimal),
|
|
TransformFromCopy(input_dot, output_dot),
|
|
)
|
|
|
|
kw = {
|
|
"run_time": 10,
|
|
"rate_func": lambda t: smooth(t, 1),
|
|
}
|
|
self.play(
|
|
ApplyMethod(input_tracker.set_value, 1, **kw),
|
|
ShowCreation(path.copy(), remover=True, **kw),
|
|
)
|
|
self.add(path)
|
|
self.add(output_dot)
|
|
self.wait()
|
|
|
|
# Flatten to 1d
|
|
real_function_word = TextMobject(
|
|
"Real-valued function"
|
|
)
|
|
real_function_word.next_to(ORIGIN, DOWN, MED_LARGE_BUFF)
|
|
path.generate_target()
|
|
path.target.stretch(0, 1)
|
|
path.target.move_to(plane.n2p(0))
|
|
|
|
self.play(
|
|
FadeIn(real_function_word),
|
|
MoveToTarget(path),
|
|
)
|
|
input_tracker.set_value(0)
|
|
self.play(
|
|
input_tracker.set_value, 1,
|
|
**kw
|
|
)
|
|
|
|
#
|
|
def get_input_line(self, input_rect):
|
|
input_line = UnitInterval()
|
|
input_line.move_to(input_rect)
|
|
input_line.shift(0.25 * UP)
|
|
input_line.set_width(
|
|
input_rect.get_width() - 1
|
|
)
|
|
input_line.add_numbers(0, 0.5, 1)
|
|
return input_line
|
|
|
|
def get_path(self):
|
|
# mob = SVGMobject("BatmanLogo")
|
|
mob = TexMobject("\\pi")
|
|
path = mob.family_members_with_points()[0]
|
|
path.set_height(3.5)
|
|
path.move_to(2 * DOWN, DOWN)
|
|
path.set_stroke(YELLOW, 2)
|
|
path.set_fill(opacity=0)
|
|
return path
|
|
|
|
|
|
class GraphForFlattenedPi(ClarifyInputAndOutput):
|
|
CONFIG = {
|
|
"camera_config": {"background_color": DARKER_GREY},
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_plane()
|
|
plane = self.plane
|
|
self.remove(plane, plane.label)
|
|
|
|
path = self.get_path()
|
|
|
|
axes = Axes(
|
|
x_min=0,
|
|
x_max=1,
|
|
x_axis_config={
|
|
"unit_size": 7,
|
|
"include_tip": False,
|
|
"tick_frequency": 0.1,
|
|
},
|
|
y_min=-1.5,
|
|
y_max=1.5,
|
|
y_axis_config={
|
|
"include_tip": False,
|
|
"unit_size": 2.5,
|
|
"tick_frequency": 0.5,
|
|
},
|
|
)
|
|
axes.set_width(FRAME_WIDTH - 1)
|
|
axes.set_height(FRAME_HEIGHT - 1, stretch=True)
|
|
axes.center()
|
|
|
|
axes.x_axis.add_numbers(
|
|
0.5, 1.0,
|
|
number_config={"num_decimal_places": 1},
|
|
)
|
|
axes.y_axis.add_numbers(
|
|
-1.0, 1.0,
|
|
number_config={"num_decimal_places": 1},
|
|
)
|
|
|
|
def func(t):
|
|
return plane.x_axis.p2n(
|
|
path.point_from_proportion(t)
|
|
)
|
|
|
|
graph = axes.get_graph(func)
|
|
graph.set_color(PINK)
|
|
|
|
v_line = always_redraw(lambda: Line(
|
|
axes.x_axis.n2p(axes.x_axis.p2n(graph.get_end())),
|
|
graph.get_end(),
|
|
stroke_width=1,
|
|
))
|
|
|
|
self.add(axes)
|
|
self.add(v_line)
|
|
|
|
kw = {
|
|
"run_time": 10,
|
|
"rate_func": lambda t: smooth(t, 1),
|
|
}
|
|
self.play(ShowCreation(graph, **kw))
|
|
self.wait()
|
|
|
|
|
|
class SimpleComplexExponentExample(ClarifyInputAndOutput):
|
|
CONFIG = {
|
|
"input_space_rect_config": {
|
|
"width": 14,
|
|
"height": 1.5,
|
|
},
|
|
"input_line_config": {
|
|
"unit_size": 0.5,
|
|
"x_min": 0,
|
|
"x_max": 25,
|
|
"stroke_width": 2,
|
|
},
|
|
"input_numbers": range(0, 30, 5),
|
|
"input_tex_args": ["t", "="],
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_plane()
|
|
self.setup_input_space()
|
|
self.setup_input_trackers()
|
|
self.setup_output_trackers()
|
|
|
|
# Testing
|
|
time = self.input_line.x_max
|
|
self.play(
|
|
self.input_tracker.set_value, time,
|
|
run_time=time,
|
|
rate_func=linear,
|
|
)
|
|
|
|
def setup_plane(self):
|
|
plane = ComplexPlane()
|
|
plane.scale(2)
|
|
plane.add_coordinates()
|
|
plane.shift(DOWN)
|
|
self.plane = plane
|
|
self.add(plane)
|
|
|
|
def setup_input_trackers(self):
|
|
input_line = self.input_line
|
|
input_tracker = ValueTracker(0)
|
|
get_input = input_tracker.get_value
|
|
|
|
input_tip = ArrowTip(start_angle=-TAU / 4)
|
|
input_tip.scale(0.5)
|
|
input_tip.set_color(PINK)
|
|
f_always(
|
|
input_tip.move_to,
|
|
lambda: input_line.n2p(get_input()),
|
|
lambda: DOWN,
|
|
)
|
|
|
|
input_label = VGroup(
|
|
TexMobject(*self.input_tex_args),
|
|
DecimalNumber(),
|
|
)
|
|
input_label[0].set_color_by_tex("t", PINK)
|
|
input_label.scale(0.7)
|
|
input_label.add_updater(
|
|
lambda m: m.arrange(RIGHT, buff=SMALL_BUFF)
|
|
)
|
|
input_label.add_updater(
|
|
lambda m: m[1].set_value(get_input())
|
|
)
|
|
input_label.add_updater(
|
|
lambda m: m.next_to(input_tip, UP, SMALL_BUFF)
|
|
)
|
|
|
|
self.input_tracker = input_tracker
|
|
self.input_tip = input_tip
|
|
self.input_label = input_label
|
|
|
|
self.add(input_tip, input_label)
|
|
|
|
def setup_output_trackers(self):
|
|
plane = self.plane
|
|
get_input = self.input_tracker.get_value
|
|
|
|
def get_output():
|
|
return np.exp(complex(0, get_input()))
|
|
|
|
def get_output_point():
|
|
return plane.n2p(get_output())
|
|
|
|
output_label, static_output_label = [
|
|
TexMobject(
|
|
"e^{i t}" + s,
|
|
tex_to_color_map={"t": PINK},
|
|
background_stroke_width=3,
|
|
)
|
|
for s in ["", "\\approx"]
|
|
]
|
|
output_label.scale(1.2)
|
|
output_label.add_updater(
|
|
lambda m: m.shift(
|
|
-m.get_bottom() +
|
|
get_output_point() +
|
|
rotate_vector(
|
|
0.35 * RIGHT,
|
|
get_input(),
|
|
)
|
|
)
|
|
)
|
|
|
|
output_vector = Vector()
|
|
output_vector.set_opacity(0.75)
|
|
output_vector.add_updater(
|
|
lambda m: m.put_start_and_end_on(
|
|
plane.n2p(0), get_output_point(),
|
|
)
|
|
)
|
|
|
|
t_max = 40
|
|
full_output_path = ParametricFunction(
|
|
lambda t: plane.n2p(np.exp(complex(0, t))),
|
|
t_min=0,
|
|
t_max=t_max
|
|
)
|
|
output_path = VMobject()
|
|
output_path.set_stroke(YELLOW, 2)
|
|
output_path.add_updater(
|
|
lambda m: m.pointwise_become_partial(
|
|
full_output_path,
|
|
0, get_input() / t_max,
|
|
)
|
|
)
|
|
|
|
static_output_label.next_to(plane.c2p(1, 1), UR)
|
|
output_decimal = DecimalNumber(
|
|
include_sign=True,
|
|
)
|
|
output_decimal.scale(0.8)
|
|
output_decimal.set_stroke(BLACK, 3, background=True)
|
|
output_decimal.add_updater(
|
|
lambda m: m.set_value(get_output())
|
|
)
|
|
output_decimal.add_updater(
|
|
lambda m: m.next_to(
|
|
static_output_label,
|
|
RIGHT, 2 * SMALL_BUFF,
|
|
aligned_edge=DOWN,
|
|
)
|
|
)
|
|
|
|
self.add(output_path)
|
|
self.add(output_vector)
|
|
self.add(output_label)
|
|
self.add(static_output_label)
|
|
self.add(BackgroundRectangle(output_decimal))
|
|
self.add(output_decimal)
|
|
|
|
#
|
|
def get_input_line(self, input_rect):
|
|
input_line = NumberLine(**self.input_line_config)
|
|
input_line.move_to(input_rect)
|
|
input_line.set_width(
|
|
input_rect.get_width() - 1.5,
|
|
stretch=True,
|
|
)
|
|
input_line.add_numbers(*self.input_numbers)
|
|
return input_line
|
|
|
|
|
|
class TRangingFrom0To1(SimpleComplexExponentExample):
|
|
CONFIG = {
|
|
"input_space_rect_config": {
|
|
"width": 6,
|
|
"height": 2,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_input_space()
|
|
self.setup_input_trackers()
|
|
|
|
self.play(
|
|
self.input_tracker.set_value, 1,
|
|
run_time=10,
|
|
rate_func=linear
|
|
)
|
|
|
|
def get_input_line(self, rect):
|
|
result = ClarifyInputAndOutput.get_input_line(self, rect)
|
|
result.stretch(0.9, 0)
|
|
result.set_stroke(width=2)
|
|
for sm in result.get_family():
|
|
if isinstance(sm, DecimalNumber):
|
|
sm.stretch(1 / 0.9, 0)
|
|
sm.set_stroke(width=0)
|
|
return result
|