Incremental progress on WindingNumber

This commit is contained in:
Sridhar Ramesh
2018-01-26 12:26:30 -08:00
parent 10cc6469ef
commit ab45afba8a

View File

@ -128,7 +128,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
downBrace, upBrace = yBraces = TexMobject("||")
yBraces.stretch(2, 0)
yBraces.rotate(np.pi/2)
yBraces.rotate(TAU/4)
lowerX = self.initial_lower_x
lowerY = self.func(lowerX)
@ -261,13 +261,14 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
# popping out and in?
#
# TODO: Perhaps have bullets change color corresponding to a function of their coordinates?
# This could involve some merging of functoinality with PiColorWakler
# This could involve some merging of functoinality with PiWalker
class LinePulser(ContinualAnimation):
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
def __init__(self, line, bullet_template, num_bullets, pulse_time, color_func = None, **kwargs):
self.line = line
self.num_bullets = num_bullets
self.pulse_time = pulse_time
self.bullets = [bullet_template.copy() for i in range(num_bullets)]
self.color_func = color_func
ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs)
def update_mobject(self, dt):
@ -275,8 +276,11 @@ class LinePulser(ContinualAnimation):
start = self.line.get_start()
end = self.line.get_end()
for i in range(self.num_bullets):
self.bullets[i].move_to(interpolate(start, end,
np.true_divide((i + alpha),(self.num_bullets))))
position = interpolate(start, end,
np.true_divide((i + alpha),(self.num_bullets)))
self.bullets[i].move_to(position)
if self.color_func:
self.bullets.set_color(self.color_func(position))
def color_func(alpha):
@ -298,7 +302,7 @@ class ArrowCircleTest(Scene):
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
def rev_rotate(x, revs):
x.rotate(revs * 2 * np.pi)
x.rotate(revs * TAU)
x.set_color(color_func(revs))
return x
@ -322,8 +326,9 @@ class FuncRotater(Animation):
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
angle_revs = self.rotate_func(alpha)
# We do a clockwise rotation
self.mobject.rotate(
angle_revs * 2 * np.pi,
-angle_revs * TAU,
about_point = ORIGIN
)
self.mobject.set_color(color_func(angle_revs))
@ -336,21 +341,40 @@ class TestRotater(Scene):
rotate_func = lambda x : x % 0.25,
run_time = 10))
# TODO: Be careful about clockwise vs. counterclockwise convention throughout!
# Make sure this is correct everywhere in resulting video.
class OdometerScene(Scene):
CONFIG = {
"rotate_func" : lambda x : np.sin(x * 2 * np.pi),
"run_time" : 5
"rotate_func" : lambda x : np.sin(x * TAU),
"run_time" : 5,
"dashed_line_angle" : None,
"biased_display_start" : None
}
def construct(self):
base_arrow = Arrow(ORIGIN, RIGHT)
circle = Circle(center = ORIGIN, radius = 1.3)
radius = 1.3
circle = Circle(center = ORIGIN, radius = radius)
self.add(circle)
if self.dashed_line_angle:
dashed_line = DashedLine(ORIGIN, radius * RIGHT)
# Clockwise rotation
dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN)
self.add(dashed_line)
num_display = DecimalNumber(0)
num_display.move_to(2 * DOWN)
display_val_bias = 0
if self.biased_display_start != None:
display_val_bias = self.biased_display_start - self.rotate_func(0)
display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias
base_arrow = Arrow(ORIGIN, RIGHT, buff = 0)
self.play(
FuncRotater(base_arrow, rotate_func = self.rotate_func),
ChangingDecimal(num_display, self.rotate_func),
ChangingDecimal(num_display, display_func),
run_time = self.run_time,
rate_func = None)
@ -361,7 +385,7 @@ def point_to_rev((x, y)):
if (x, y) == (0, 0):
print "Error! Angle of (0, 0) computed!"
return None
return np.true_divide(np.arctan2(y, x), 2 * np.pi)
return np.true_divide(np.arctan2(y, x), TAU)
# Returns the value with the same fractional component as x, closest to m
def resit_near(x, m):
@ -441,6 +465,8 @@ class RectangleData():
sides = (self.get_top(), self.get_bottom())
elif dim == 1:
sides = (self.get_left(), self.get_right())
else:
print "RectangleData.split_line_on_dim passed illegitimate dimension!"
return tuple([mid(x, y) for (x, y) in sides])
@ -455,10 +481,120 @@ def plane_poly_with_roots(*points):
def plane_func_from_complex_func(f):
return lambda (x, y) : complex_to_pair(f(complex(x,y)))
def point_func_from_complex_func(f):
return lambda (x, y, z): complex_to_R3(f(complex(x, y)))
empty_animation = Animation(Mobject())
def EmptyAnimation():
return empty_animation
class WalkerAnimation(Animation):
CONFIG = {
"walk_func" : None, # Must be initialized to use
"remover" : True,
"rate_func" : None,
"coords_to_point" : None
}
def __init__(self, walk_func, rev_func, coords_to_point, **kwargs):
self.walk_func = walk_func
self.rev_func = rev_func
self.coords_to_point = coords_to_point
self.compound_walker = VGroup()
self.compound_walker.walker = PiCreature(color = RED)
self.compound_walker.walker.scale(0.35)
self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0)
self.compound_walker.digest_mobject_attrs()
Animation.__init__(self, self.compound_walker, **kwargs)
# Perhaps abstract this out into an "Animation updating from original object" class
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.points = np.array(starting_submobject.points)
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
cur_x, cur_y = cur_coords = self.walk_func(alpha)
self.mobject.walker.move_to(self.coords_to_point(cur_x, cur_y))
rev = self.rev_func(cur_coords)
self.mobject.walker.set_color(color_func(rev))
self.mobject.arrow.set_color(color_func(rev))
self.mobject.arrow.rotate(
rev * TAU,
about_point = ORIGIN #self.mobject.arrow.get_start()
)
def LinearWalker(start_coords, end_coords, coords_to_point, rev_func, **kwargs):
walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha)
return WalkerAnimation(
walk_func = walk_func,
coords_to_point = coords_to_point,
rev_func = rev_func,
**kwargs)
class PiWalker(Scene):
CONFIG = {
"func" : plane_func_from_complex_func(lambda c : c**2),
"walk_coords" : [],
"step_run_time" : 1
}
def construct(self):
rev_func = lambda p : point_to_rev(self.func(p))
num_plane = NumberPlane()
num_plane.fade()
self.add(num_plane)
walk_coords = self.walk_coords
for i in range(len(walk_coords)):
start_x, start_y = start_coords = walk_coords[i]
start_point = num_plane.coords_to_point(start_x, start_y)
end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)]
end_point = num_plane.coords_to_point(end_x, end_y)
self.play(
LinearWalker(
start_coords = start_coords,
end_coords = end_coords,
coords_to_point = num_plane.coords_to_point,
rev_func = rev_func,
remover = (i < len(walk_coords) - 1)
),
ShowCreation(Line(start_point, end_point)),
run_time = self.step_run_time)
self.wait()
class PiWalkerRect(PiWalker):
CONFIG = {
"start_x" : -1,
"start_y" : 1,
"walk_width" : 2,
"walk_height" : 2,
}
def setup(self):
TL = np.array((self.start_x, self.start_y))
TR = TL + (self.walk_width, 0)
BR = TR + (0, -self.walk_height)
BL = BR + (-self.walk_width, 0)
self.walk_coords = [TL, TR, BR, BL]
PiWalker.setup(self)
class PiWalkerCircle(PiWalker):
CONFIG = {
"radius" : 1,
"num_steps" : 100,
"step_run_time" : 0.01
}
def setup(self):
r = self.radius
N = self.num_steps
self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)]
PiWalker.setup(self)
# TODO: Perhaps restructure this to avoid using AnimationGroup, and instead
# use lists of animations or lists or other such data, to be merged and processed into parallel
# animations later
@ -495,8 +631,17 @@ class EquationSolver2d(Scene):
color = RED)
thin_line = line.copy()
thin_line.set_stroke(width = 1)
walker_anim = LinearWalker(
start_coords = start,
end_coords = end,
coords_to_point = num_plane.coords_to_point,
rev_func = rev_func,
remover = True
)
line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim,
run_time = 2)
anim = Succession(
ShowCreation, line,
line_draw_anim,
Transform, line, thin_line
)
return (anim, rebased_winder(1))
@ -523,6 +668,8 @@ class EquationSolver2d(Scene):
rect.get_bottom_left()
]
points = [num_plane.coords_to_point(x, y) for (x, y) in coords]
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating
# their "Nothing here" status?
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED)
return Succession(anim, FadeIn(fill_rect))
else:
@ -541,7 +688,7 @@ class EquationSolver2d(Scene):
mid_line = DashedLine(*mid_line_points)
return Succession(anim,
ShowCreation(mid_line),
FadeOut(mid_line),
# FadeOut(mid_line), # TODO: Can change timing so this fades out at just the time it would be overdrawn
AnimationGroup(*sub_anims)
)
@ -565,80 +712,6 @@ class EquationSolver2d(Scene):
self.wait()
class PiColorWalker(Scene):
CONFIG = {
"func" : plane_func_from_complex_func(lambda c : c**2),
"start_x" : -1,
"start_y" : 1,
"walk_width" : 2,
"walk_height" : 2,
}
def construct(self):
rev_func = lambda p : point_to_rev(self.func(p))
num_plane = NumberPlane()
num_plane.fade()
self.add(num_plane)
class WalkerAnimation(Animation):
CONFIG = {
"remover" : True,
"rate_func" : None,
"start_coords" : None,
"end_coords" : None,
}
def __init__(self, start_coords, end_coords, **kwargs):
self.start_coords = start_coords
self.end_coords = end_coords
self.compound_walker = VGroup()
self.compound_walker.walker = PiCreature(color = RED)
self.compound_walker.walker.scale(0.35)
self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0)
self.compound_walker.digest_mobject_attrs()
Animation.__init__(self, self.compound_walker, **kwargs)
# Perhaps abstract this out into an "Animation updating from original object" class
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.points = np.array(starting_submobject.points)
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
cur_x, cur_y = cur_coords = interpolate(self.start_coords, self.end_coords, alpha)
self.mobject.walker.move_to(num_plane.coords_to_point(cur_x, cur_y))
rev = rev_func(cur_coords)
self.mobject.walker.set_color(color_func(rev))
self.mobject.arrow.set_color(color_func(rev))
self.mobject.arrow.rotate(
rev * 2 * np.pi,
about_point = ORIGIN #self.mobject.arrow.get_start()
)
TL = np.array((self.start_x, self.start_y))
TR = TL + (self.walk_width, 0)
BR = TR + (0, -self.walk_height)
BL = BR + (-self.walk_width, 0)
walk_coords = [TL, TR, BR, BL]
for i in range(len(walk_coords)):
start_x, start_y = start_coords = walk_coords[i]
start_point = num_plane.coords_to_point(start_x, start_y)
end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)]
end_point = num_plane.coords_to_point(end_x, end_y)
self.play(
WalkerAnimation(
start_coords = start_coords,
end_coords = end_coords,
remover = (i < len(walk_coords) - 1)
),
ShowCreation(Line(start_point, end_point)))
self.wait()
#############
# Above are mostly general tools; here, we list, in order, finished or near-finished scenes
@ -679,31 +752,62 @@ class NumberLineScene(Scene):
# TODO: Add labels, arrows, specific points
self.wait()
interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1),
stroke_color = RED, stroke_width = 10)
self.play(ShowCreation(interval_1d))
border_color = PURPLE_E
inner_color = RED
stroke_width = 10
left_point = num_line.number_to_point(-1)
right_point = num_line.number_to_point(1)
interval_1d = Line(left_point, right_point,
stroke_color = inner_color, stroke_width = stroke_width)
left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color)
right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color)
endpoints_1d = VGroup(left_dot, right_dot)
full_1d = VGroup(interval_1d, endpoints_1d)
self.play(ShowCreation(full_1d))
self.wait()
num_plane = NumberPlane()
random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT]
random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT]
interval_2d = Polygon(
border_2d = Polygon(
*random_points,
stroke_color = RED,
stroke_width = 10)
# TODO: Turn this into a more complicated, curvy loop?
# TODO: Illustrate borders and filled interiors with a particular color
# on both 1d and 2d region?
stroke_color = border_color,
stroke_width = stroke_width)
filling_2d = Polygon(
*random_points,
fill_color = inner_color,
fill_opacity = 0.8,
stroke_width = stroke_width)
full_2d = VGroup(filling_2d, border_2d)
self.play(
FadeOut(num_line),
FadeIn(num_plane),
ReplacementTransform(interval_1d, interval_2d))
ReplacementTransform(full_1d, full_2d))
self.wait()
# TODO: Split screen illustration of 2d function (before domain coloring)
class Initial2dFuncScene(Scene):
def setup(self):
left_camera = Camera(**self.camera_config)
right_camera = MappingCamera(
mapping_func = point_func_from_complex_func(lambda c : np.exp(c)),
**self.camera_config)
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
self.camera = split_screen_camera
def construct(self):
num_plane = NumberPlane()
num_plane.fade()
self.add(num_plane)
points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP]
for i in range(len(points) - 1):
line = Line(points[i], points[i + 1], color = RED)
self.play(ShowCreation(line))
# TODO: Illustrations for introducing domain coloring
@ -844,13 +948,13 @@ class LoopSplitScene(Scene):
self.wait()
# Is there a way to abstract this into a general process to derive a new mapped scene from an old scene?
class LoopSplitSceneMapped(LoopSplitScene):
def setup(self):
left_camera = Camera(**self.camera_config)
right_camera = MappingCamera(
mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3),
allow_object_intrusion = False
**self.camera_config)
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
self.camera = split_screen_camera
@ -860,11 +964,35 @@ class LoopSplitSceneMapped(LoopSplitScene):
class FundThmAlg(EquationSolver2d):
CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)),
"num_iterations" : 1,
}
# TODO: Borsuk-Ulam visuals
# Note: May want to do an ordinary square scene, then mapping func it into a circle
# class BorsukUlamScene(PiWalker):
# TODO: "Good enough" property visuals (swinging arrows and odometer, like in odometer scene above)
# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere
left_func = lambda x : x**2 - x + 1
diff_func = lambda x : np.cos(1.4 * (x - 0.1) * (np.log(x + 0.1) - 0.3) * TAU)/2.1
class LeftOdometer(OdometerScene):
CONFIG = {
"rotate_func" : left_func,
"biased_display_start" : 0
}
class RightOdometer(OdometerScene):
CONFIG = {
"rotate_func" : lambda x : left_func(x) + diff_func(x),
"biased_display_start" : 0
}
class DiffOdometer(OdometerScene):
CONFIG = {
"rotate_func" : diff_func,
"dashed_line_angle" : 0.5,
"biased_display_start" : 0
}
# TODO: Brouwer's fixed point theorem visuals
@ -876,16 +1004,18 @@ class FundThmAlg(EquationSolver2d):
# Minor fiddling with little things in each animation; placements, colors, timing
# Writing new Pi creature walker scenes off of general template
# Odometer/swinging arrows stuff
# Pi creature emotion stuff
# Writing new Pi creature walker scenes off of general template
# Split screen illustration of 2d function (before domain coloring)
# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out
# ----
# Pi creature emotion stuff
# BFT visuals
# Borsuk-Ulam visuals