Files
manim/3b1b_projects/active/diffyq/part4/complex_functions.py

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