Files
manim/active_projects/WindingNumber.py

1552 lines
56 KiB
Python

from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import *
from animation.continual_animation import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import *
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.graph_scene import *
# TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code,
# (and it will be done, but first I'll figure out what I'm doing with all this...)
# -SR
def rev_to_rgba(alpha):
alpha = (0.5 - alpha) % 1 # For convenience, to go CW from red on left instead of CCW from right
# 0 is red, 1/6 is yellow, 1/3 is green, 2/3 is blue
hue_list = [0, 0.5/6.0, 1/6.0, 1.1/6.0, 2/6.0, 3/6.0, 4/6.0, 5/6.0]
num_hues = len(hue_list)
start_index = int(np.floor(num_hues * alpha)) % num_hues
end_index = (start_index + 1) % num_hues
beta = (alpha % (1.0/num_hues)) * num_hues
start_hue = hue_list[start_index]
end_hue = hue_list[end_index]
if end_hue < start_hue:
end_hue = end_hue + 1
hue = interpolate(start_hue, end_hue, beta)
return color_to_rgba(Color(hue = hue, saturation = 1, luminance = 0.5))
# alpha = alpha % 1
# colors = colorslist
# num_colors = len(colors)
# beta = (alpha % (1.0/num_colors)) * num_colors
# start_index = int(np.floor(num_colors * alpha)) % num_colors
# end_index = (start_index + 1) % num_colors
# return interpolate(colors[start_index], colors[end_index], beta)
def rev_to_color(alpha):
return rgba_to_color(rev_to_rgba(alpha))
def point_to_rev((x, y), allow_origin = False):
# Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to
# design choices in the underlying atan2 library call, but for our purposes, this is
# illegitimate, and all winding number calculations must be set up to avoid this
if not(allow_origin) and (x, y) == (0, 0):
print "Error! Angle of (0, 0) computed!"
return
return fdiv(np.arctan2(y, x), TAU)
def point_to_rgba(point):
rev = point_to_rev(point, allow_origin = True)
rgba = rev_to_rgba(rev)
base_size = np.sqrt(point[0]**2 + point[1]**2)
rescaled_size = np.sqrt(base_size/(base_size + 1))
return rgba * rescaled_size
positive_color = rev_to_color(0)
negative_color = rev_to_color(0.5)
neutral_color = rev_to_color(0.25)
class EquationSolver1d(GraphScene, ZoomedScene):
CONFIG = {
"camera_config" :
{
"use_z_coordinate_for_display_order": True,
},
"func" : lambda x : x,
"targetX" : 0,
"targetY" : 0,
"initial_lower_x" : 0,
"initial_upper_x" : 10,
"num_iterations" : 10,
"iteration_at_which_to_start_zoom" : None,
"graph_label" : None,
"show_target_line" : True
}
def drawGraph(self):
self.setup_axes()
self.graph = self.get_graph(self.func)
self.add(self.graph)
if self.graph_label != None:
curve_label = self.get_graph_label(self.graph, self.graph_label,
x_val = 4, direction = LEFT)
curve_label.shift(LEFT)
self.add(curve_label)
if self.show_target_line:
target_line_object = DashedLine(
self.coords_to_point(self.x_min, self.targetY),
self.coords_to_point(self.x_max, self.targetY),
dashed_segment_length = 0.1)
self.add(target_line_object)
target_line_label = TexMobject("y = " + str(self.targetY))
target_line_label.next_to(target_line_object.get_left(), UP + RIGHT)
self.add(target_line_label)
self.wait() # Give us time to appreciate the graph
self.play(FadeOut(target_line_label)) # Reduce clutter
print "For reference, graphOrigin: ", self.coords_to_point(0, 0)
print "targetYPoint: ", self.coords_to_point(0, self.targetY)
# This is a mess right now (first major animation coded),
# but it works; can be refactored later or never
def solveEquation(self):
leftBrace = TexMobject("|") # Not using [ and ] because they end up crossing over
leftBrace.set_color(negative_color)
rightBrace = TexMobject("|")
rightBrace.set_color(positive_color)
xBraces = Group(leftBrace, rightBrace)
xBraces.stretch(2, 0)
downBrace = TexMobject("|")
downBrace.set_color(negative_color)
upBrace = TexMobject("|")
upBrace.set_color(positive_color)
yBraces = Group(downBrace, upBrace)
yBraces.stretch(2, 0)
yBraces.rotate(TAU/4)
lowerX = self.initial_lower_x
lowerY = self.func(lowerX)
upperX = self.initial_upper_x
upperY = self.func(upperX)
leftBrace.move_to(self.coords_to_point(lowerX, 0)) #, aligned_edge = RIGHT)
leftBraceLabel = DecimalNumber(lowerX)
leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF)
leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel,
lambda alpha : self.point_to_coords(leftBrace.get_center())[0],
tracked_mobject = leftBrace)
self.add(leftBraceLabelAnimation)
rightBrace.move_to(self.coords_to_point(upperX, 0)) #, aligned_edge = LEFT)
rightBraceLabel = DecimalNumber(upperX)
rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF)
rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel,
lambda alpha : self.point_to_coords(rightBrace.get_center())[0],
tracked_mobject = rightBrace)
self.add(rightBraceLabelAnimation)
downBrace.move_to(self.coords_to_point(0, lowerY)) #, aligned_edge = UP)
downBraceLabel = DecimalNumber(lowerY)
downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF)
downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel,
lambda alpha : self.point_to_coords(downBrace.get_center())[1],
tracked_mobject = downBrace)
self.add(downBraceLabelAnimation)
upBrace.move_to(self.coords_to_point(0, upperY)) #, aligned_edge = DOWN)
upBraceLabel = DecimalNumber(upperY)
upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF)
upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel,
lambda alpha : self.point_to_coords(upBrace.get_center())[1],
tracked_mobject = upBrace)
self.add(upBraceLabelAnimation)
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
lowerDotXPoint = self.coords_to_point(lowerX, 0)
lowerDotYPoint = self.coords_to_point(0, self.func(lowerX))
lowerDot = Dot(lowerDotPoint + OUT, color = negative_color)
upperDotPoint = self.input_to_graph_point(upperX, self.graph)
upperDot = Dot(upperDotPoint + OUT, color = positive_color)
upperDotXPoint = self.coords_to_point(upperX, 0)
upperDotYPoint = self.coords_to_point(0, self.func(upperX))
lowerXLine = Line(lowerDotXPoint, lowerDotPoint, color = negative_color)
upperXLine = Line(upperDotXPoint, upperDotPoint, color = positive_color)
lowerYLine = Line(lowerDotYPoint, lowerDotPoint, color = negative_color)
upperYLine = Line(upperDotYPoint, upperDotPoint, color = positive_color)
self.add(lowerXLine, upperXLine, lowerYLine, upperYLine)
self.add(xBraces, yBraces, lowerDot, upperDot)
x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = neutral_color)
self.add(x_guess_line)
lowerGroup = Group(
lowerDot,
leftBrace, downBrace,
lowerXLine, lowerYLine,
x_guess_line
)
upperGroup = Group(
upperDot,
rightBrace, upBrace,
upperXLine, upperYLine,
x_guess_line
)
initialLowerXDot = Dot(lowerDotXPoint, color = negative_color)
initialUpperXDot = Dot(upperDotXPoint, color = positive_color)
initialLowerYDot = Dot(lowerDotYPoint, color = negative_color)
initialUpperYDot = Dot(upperDotYPoint, color = positive_color)
self.add(initialLowerXDot, initialUpperXDot, initialLowerYDot, initialUpperYDot)
for i in range(self.num_iterations):
if i == self.iteration_at_which_to_start_zoom:
self.activate_zooming()
self.little_rectangle.move_to(
self.coords_to_point(self.targetX, self.targetY))
inverseZoomFactor = 1/float(self.zoom_factor)
self.play(
lowerDot.scale_in_place, inverseZoomFactor,
upperDot.scale_in_place, inverseZoomFactor)
def makeUpdater(xAtStart, fixed_guess_x):
def updater(group, alpha):
dot, xBrace, yBrace, xLine, yLine, guess_line = group
newX = interpolate(xAtStart, midX, alpha)
newY = self.func(newX)
graphPoint = self.input_to_graph_point(newX,
self.graph)
dot.move_to(graphPoint)
xAxisPoint = self.coords_to_point(newX, 0)
xBrace.move_to(xAxisPoint)
yAxisPoint = self.coords_to_point(0, newY)
yBrace.move_to(yAxisPoint)
xLine.put_start_and_end_on(xAxisPoint, graphPoint)
yLine.put_start_and_end_on(yAxisPoint, graphPoint)
fixed_guess_point = self.coords_to_point(fixed_guess_x, 0)
guess_line.put_start_and_end_on(xAxisPoint, fixed_guess_point)
return group
return updater
midX = (lowerX + upperX)/float(2)
midY = self.func(midX)
in_negative_branch = midY < self.targetY
sign_color = negative_color if in_negative_branch else positive_color
midCoords = self.coords_to_point(midX, midY)
midColor = neutral_color
# Hm... even the z buffer isn't helping keep this above x_guess_line
midXPoint = Dot(self.coords_to_point(midX, 0) + OUT, color = midColor)
x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor)
x_guess_label_num = DecimalNumber(midX, fill_color = midColor)
x_guess_label_num.move_to(0.9 * SPACE_HEIGHT * DOWN)
x_guess_label_caption.next_to(x_guess_label_num, LEFT)
x_guess_label = Group(x_guess_label_caption, x_guess_label_num)
y_guess_label_caption = TextMobject(", y = ", fill_color = midColor)
y_guess_label_num = DecimalNumber(midY, fill_color = sign_color)
y_guess_label_caption.next_to(x_guess_label_num, RIGHT)
y_guess_label_num.next_to(y_guess_label_caption, RIGHT)
y_guess_label = Group(y_guess_label_caption, y_guess_label_num)
guess_labels = Group(x_guess_label, y_guess_label)
self.play(
ReplacementTransform(leftBrace.copy(), midXPoint),
ReplacementTransform(rightBrace.copy(), midXPoint),
FadeIn(x_guess_label))
midXLine = DashedLine(self.coords_to_point(midX, 0), midCoords, color = midColor)
self.play(ShowCreation(midXLine))
midDot = Dot(midCoords, color = sign_color)
if(self.iteration_at_which_to_start_zoom != None and
i >= self.iteration_at_which_to_start_zoom):
midDot.scale_in_place(inverseZoomFactor)
self.add(midDot)
midYLine = DashedLine(midCoords, self.coords_to_point(0, midY), color = sign_color)
self.play(
ShowCreation(midYLine),
FadeIn(y_guess_label),
ApplyMethod(midXPoint.set_color, sign_color),
ApplyMethod(midXLine.set_color, sign_color))
midYPoint = Dot(self.coords_to_point(0, midY), color = sign_color)
self.add(midYPoint)
if in_negative_branch:
self.play(
UpdateFromAlphaFunc(lowerGroup,
makeUpdater(lowerX,
fixed_guess_x = upperX
)
),
FadeOut(guess_labels),
)
lowerX = midX
lowerY = midY
else:
self.play(
UpdateFromAlphaFunc(upperGroup,
makeUpdater(upperX,
fixed_guess_x = lowerX
)
),
FadeOut(guess_labels),
)
upperX = midX
upperY = midY
#mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected?
self.remove(midXLine, midDot, midYLine)
self.wait()
def construct(self):
self.drawGraph()
self.solveEquation()
# Returns the value with the same fractional component as x, closest to m
def resit_near(x, m):
frac_diff = (x - m) % 1
if frac_diff > 0.5:
frac_diff -= 1
return m + frac_diff
# TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating
# latter as needed from former?
def make_alpha_winder(func, start, end, num_checkpoints):
check_points = [None for i in range(num_checkpoints)]
check_points[0] = func(start)
step_size = fdiv(end - start, num_checkpoints)
for i in range(num_checkpoints - 1):
check_points[i + 1] = \
resit_near(
func(start + (i + 1) * step_size),
check_points[i])
def return_func(alpha):
index = clamp(0, num_checkpoints - 1, int(alpha * num_checkpoints))
x = interpolate(start, end, alpha)
return resit_near(func(x), check_points[index])
return return_func
def split_interval((a, b)):
mid = (a + b)/2.0
return ((a, mid), (mid, b))
class RectangleData():
def __init__(self, x_interval, y_interval):
self.rect = (x_interval, y_interval)
def get_top_left(self):
return np.array((self.rect[0][0], self.rect[1][1]))
def get_top_right(self):
return np.array((self.rect[0][1], self.rect[1][1]))
def get_bottom_right(self):
return np.array((self.rect[0][1], self.rect[1][0]))
def get_bottom_left(self):
return np.array((self.rect[0][0], self.rect[1][0]))
def get_top(self):
return (self.get_top_left(), self.get_top_right())
def get_right(self):
return (self.get_top_right(), self.get_bottom_right())
def get_bottom(self):
return (self.get_bottom_right(), self.get_bottom_left())
def get_left(self):
return (self.get_bottom_left(), self.get_top_left())
def splits_on_dim(self, dim):
x_interval = self.rect[0]
y_interval = self.rect[1]
# TODO: Can refactor the following; will do later
if dim == 0:
return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)]
elif dim == 1:
return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)[::-1]]
else:
print "RectangleData.splits_on_dim passed illegitimate dimension!"
return tuple(return_data)
def split_line_on_dim(self, dim):
x_interval = self.rect[0]
y_interval = self.rect[1]
if dim == 0:
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])
def complex_to_pair(c):
return np.array((c.real, c.imag))
def plane_poly_with_roots(*points):
def f((x, y)):
return complex_to_pair(np.prod([complex(x, y) - complex(a,b) for (a,b) in points]))
return f
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)))
test_map_func = point_func_from_complex_func(lambda c: c**2)
empty_animation = EmptyAnimation()
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, show_arrows = True, **kwargs):
self.walk_func = walk_func
self.rev_func = rev_func
self.coords_to_point = coords_to_point
self.compound_walker = VGroup()
self.show_arrows = show_arrows
base_walker = Dot().scale(5) # PiCreature().scale(0.8) #
self.compound_walker.walker = base_walker.scale(0.35).set_stroke(BLACK, 1.5) #PiCreature()
if show_arrows:
self.compound_walker.arrow = Arrow(ORIGIN, 0.5 * RIGHT, buff = 0).set_stroke(BLACK, 1.5)
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)
cur_point = self.coords_to_point(cur_x, cur_y)
self.mobject.shift(cur_point - self.mobject.walker.get_center())
rev = self.rev_func(cur_coords)
self.mobject.walker.set_fill(rev_to_color(rev))
if self.show_arrows:
self.mobject.arrow.set_fill(rev_to_color(rev))
self.mobject.arrow.rotate(
rev * TAU,
about_point = self.mobject.arrow.get_start()
)
def walker_animation_with_display(
walk_func,
rev_func,
coords_to_point,
number_update_func = None,
show_arrows = True,
**kwargs
):
walker_anim = WalkerAnimation(
walk_func = walk_func,
rev_func = rev_func,
coords_to_point = coords_to_point,
show_arrows = show_arrows,
**kwargs)
walker = walker_anim.compound_walker.walker
if number_update_func != None:
display = DecimalNumber(0,
num_decimal_points = 1,
fill_color = WHITE,
include_background_rectangle = True)
display.background_rectangle.fill_opacity = 0.5
display.background_rectangle.fill_color = GREY
display.background_rectangle.scale(1.2)
displaycement = 0.5 * DOWN # How about that pun, eh?
display.move_to(walker.get_center() + displaycement)
display_anim = ChangingDecimal(display,
number_update_func,
tracked_mobject = walker_anim.compound_walker.walker,
**kwargs)
anim_group = AnimationGroup(walker_anim, display_anim)
return anim_group
else:
return walker_anim
def LinearWalker(
start_coords,
end_coords,
coords_to_point,
rev_func,
number_update_func = None,
show_arrows = True,
**kwargs
):
walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha)
return walker_animation_with_display(
walk_func = walk_func,
coords_to_point = coords_to_point,
rev_func = rev_func,
number_update_func = number_update_func,
show_arrows = show_arrows,
**kwargs)
class ColorMappedByFuncScene(Scene):
CONFIG = {
"func" : lambda p : p,
"num_plane" : NumberPlane(),
"show_num_plane" : True,
"show_output" : False, # Not currently implemented; TODO
}
def setup(self):
if self.show_output:
self.input_to_pos_func = self.func
self.pos_to_color_func = lambda p : p
else:
self.input_to_pos_func = lambda p : p
self.pos_to_color_func = self.func
# func_hash hashes the function at some random points
func_hash_points = [(-0.93, 1), (1, -2.7), (20, 4)]
to_hash = tuple((self.func(p)[0], self.func(p)[1]) for p in func_hash_points)
func_hash = hash(to_hash)
full_hash = hash((func_hash, self.camera.pixel_shape))
self.background_image_file = "color_mapped_background_" + str(full_hash)
try:
file_path = get_full_raster_image_path(self.background_image_file)
# If we succeed in finding the file:
self.in_background_pass = False
except IOError:
file_path = os.path.join(RASTER_IMAGE_DIR, self.background_image_file + ".png")
self.in_background_pass = True
print "Background file: " + file_path
if self.in_background_pass:
print "The background file does not exist yet; this will be the background creation pass"
print "If not already doing so, please re-run this render with the flags -s -n 0,1 -o \"%s\""%file_path
self.show_num_plane = False
else:
print "The background file already exists; this will be the video pass as usual"
def construct(self):
if self.show_num_plane:
self.num_plane.fade()
self.add(self.num_plane)
if self.in_background_pass:
self.camera.set_background_from_func(
lambda (x, y): point_to_rgba(
self.pos_to_color_func(
# Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])),
# but for cheapness, we'll go with just (x, y), having never altered
# any num_plane's from default settings so far
(x, y)
)
)
)
# The one scene to be rendered by the desired -s -n 0, 1 invocation:
self.play(EmptyAnimation())
self.wait()
else:
self.camera.background_image = self.background_image_file
self.camera.init_background()
class PureColorMap(ColorMappedByFuncScene):
CONFIG = {
"show_num_plane" : False
}
class ColorMappedByFuncStill(ColorMappedByFuncScene):
def construct(self):
ColorMappedByFuncScene.construct(self)
self.wait()
class PiWalker(ColorMappedByFuncScene):
CONFIG = {
"walk_coords" : [],
"step_run_time" : 1,
}
def construct(self):
ColorMappedByFuncScene.construct(self)
num_plane = self.num_plane
rev_func = lambda p : point_to_rev(self.func(p))
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(
ShowCreation(Line(start_point, end_point), rate_func = None),
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),
show_arrows = not self.show_output
),
run_time = self.step_run_time)
# TODO: Allow smooth paths instead of breaking them up into lines, and
# use point_from_proportion to get points along the way
self.wait()
class PiWalkerRect(PiWalker):
CONFIG = {
"start_x" : -1,
"start_y" : 1,
"walk_width" : 2,
"walk_height" : 2,
"func" : plane_func_from_complex_func(lambda c: c**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: Give drawn lines a bit of buffer, so that the rectangle's corners are filled in
class EquationSolver2d(ColorMappedByFuncScene):
CONFIG = {
"camera_config" : {"use_z_coordinate_for_display_order": True},
"initial_lower_x" : -5.1,
"initial_upper_x" : 5.1,
"initial_lower_y" : -3.1,
"initial_upper_y" : 3.1,
"num_iterations" : 5,
"num_checkpoints" : 10,
"display_in_parallel" : True,
"use_fancy_lines" : True,
# TODO: Consider adding a "find_all_roots" flag, which could be turned off
# to only explore one of the two candidate subrectangles when both are viable
}
def construct(self):
ColorMappedByFuncScene.construct(self)
num_plane = self.num_plane
self.remove(num_plane)
background = self.camera.background
self.camera.init_background() # Clearing background
rev_func = lambda p : point_to_rev(self.func(p))
clockwise_rev_func = lambda p : -rev_func(p)
def Animate2dSolver(cur_depth, rect, dim_to_split, sides_to_draw = [0, 1, 2, 3]):
print "Solver at depth: " + str(cur_depth)
if cur_depth >= self.num_iterations:
return empty_animation
def draw_line_return_wind(start, end, start_wind, should_linger = False, draw_line = True):
alpha_winder = make_alpha_winder(clockwise_rev_func, start, end, self.num_checkpoints)
a0 = alpha_winder(0)
rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind
thick_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
stroke_width = 10,
color = RED)
if self.use_fancy_lines:
colored_line = thick_line.color_using_background_image(self.background_image_file)
# colored_line.set_background_array(background)
else:
colored_line = thick_line.set_stroke(width = 4)
walker_anim = LinearWalker(
start_coords = start,
end_coords = end,
coords_to_point = num_plane.coords_to_point,
rev_func = rev_func,
number_update_func = rebased_winder,
remover = True
)
if should_linger: # Do we need an "and not self.display_in_parallel" here?
rate_func = lingering
else:
rate_func = None
opt_line_anim = ShowCreation(colored_line) if draw_line else empty_animation
line_draw_anim = AnimationGroup(
opt_line_anim,
walker_anim,
rate_func = rate_func)
return (line_draw_anim, rebased_winder(1))
wind_so_far = 0
anim = empty_animation
sides = [
rect.get_top(),
rect.get_right(),
rect.get_bottom(),
rect.get_left()
]
for (i, (start, end)) in enumerate(sides):
(next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far,
should_linger = i == len(sides) - 1,
draw_line = i in sides_to_draw)
anim = Succession(anim, next_anim)
total_wind = round(wind_so_far)
if total_wind == 0:
coords = [
rect.get_top_left(),
rect.get_top_right(),
rect.get_bottom_right(),
rect.get_bottom_left()
]
points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + 2 * IN
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating
# their "Nothing here" status?
# Or draw a large X or something
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = DARK_BROWN)
return Succession(anim, FadeIn(fill_rect))
else:
(sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split)
if dim_to_split == 0:
sub_rect_and_sides = [(sub_rect1, 1), (sub_rect2, 3)]
else:
sub_rect_and_sides = [(sub_rect1, 2), (sub_rect2, 0)]
sub_anims = [
Animate2dSolver(
cur_depth = cur_depth + 1,
rect = sub_rect,
dim_to_split = 1 - dim_to_split,
sides_to_draw = [side_to_draw]
)
for (sub_rect, side_to_draw) in sub_rect_and_sides
]
mid_line_coords = rect.split_line_on_dim(dim_to_split)
mid_line_points = [num_plane.coords_to_point(x, y) + IN for (x, y) in mid_line_coords]
# TODO: Have this match rectangle line style, apart from dashes and thin-ness?
# Though there is also informational value in seeing the dashed line separately from rectangle lines
mid_line = DashedLine(*mid_line_points)
if self.display_in_parallel:
recursive_anim = AnimationGroup(*sub_anims)
else:
recursive_anim = Succession(*sub_anims)
return Succession(anim,
ShowCreation(mid_line),
recursive_anim
)
lower_x = self.initial_lower_x
upper_x = self.initial_upper_x
lower_y = self.initial_lower_y
upper_y = self.initial_upper_y
x_interval = (lower_x, upper_x)
y_interval = (lower_y, upper_y)
rect = RectangleData(x_interval, y_interval)
print "Starting to compute anim"
anim = Animate2dSolver(
cur_depth = 0,
rect = rect,
dim_to_split = 0,
)
print "Done computing anim"
self.play(anim)
self.wait()
# TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly
# 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 PiWalker
class LinePulser(ContinualAnimation):
def __init__(self, line, bullet_template, num_bullets, pulse_time, output_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.output_func = output_func
ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs)
def update_mobject(self, dt):
alpha = self.external_time % self.pulse_time
start = self.line.get_start()
end = self.line.get_end()
for i in range(self.num_bullets):
position = interpolate(start, end,
fdiv((i + alpha),(self.num_bullets)))
self.bullets[i].move_to(position)
if self.output_func:
position_2d = (position[0], position[1])
rev = point_to_rev(self.output_func(position_2d))
color = rev_to_color(rev)
self.bullets[i].set_color(color)
class ArrowCircleTest(Scene):
def construct(self):
circle_radius = 3
circle = Circle(radius = circle_radius, color = WHITE)
self.add(circle)
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
def rev_rotate(x, revs):
x.rotate(revs * TAU, about_point = ORIGIN)
x.set_color(rev_to_color(revs))
return x
num_arrows = 8 * 3
# 0.5 - fdiv below so as to get a clockwise rotation from left
arrows = [rev_rotate(base_arrow.copy(), 0.5 - (fdiv(i, num_arrows))) for i in range(num_arrows)]
arrows_vgroup = VGroup(*arrows)
self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None)
self.wait()
class FuncRotater(Animation):
CONFIG = {
"rotate_func" : lambda x : x # Func from alpha to revolutions
}
# 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)
angle_revs = self.rotate_func(alpha)
# We do a clockwise rotation
self.mobject.rotate(
-angle_revs * TAU,
about_point = ORIGIN
)
self.mobject.set_color(rev_to_color(angle_revs))
class TestRotater(Scene):
def construct(self):
test_line = Line(ORIGIN, RIGHT)
self.play(FuncRotater(
test_line,
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 * TAU),
"run_time" : 5,
"dashed_line_angle" : None,
"biased_display_start" : None
}
def construct(self):
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, include_background_rectangle = False).set_stroke(1)
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, display_func),
run_time = self.run_time,
rate_func = None)
#############
# Above are mostly general tools; here, we list, in order, finished or near-finished scenes
class FirstSqrtScene(EquationSolver1d):
CONFIG = {
"x_min" : 0,
"x_max" : 2.5,
"y_min" : 0,
"y_max" : 2.5**2,
"graph_origin" : 2.5*DOWN + 5.5*LEFT,
"x_axis_width" : 12,
"zoom_factor" : 3,
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
"func" : lambda x : x**2,
"targetX" : np.sqrt(2),
"targetY" : 2,
"initial_lower_x" : 1,
"initial_upper_x" : 2,
"num_iterations" : 3,
"iteration_at_which_to_start_zoom" : 3,
"graph_label" : "y = x^2",
"show_target_line" : True,
}
FirstSqrtSceneConfig = FirstSqrtScene.CONFIG
shiftVal = FirstSqrtSceneConfig["targetY"]
class SecondSqrtScene(FirstSqrtScene):
CONFIG = {
"func" : lambda x : FirstSqrtSceneConfig["func"](x) - shiftVal,
"targetY" : 0,
"graph_label" : FirstSqrtSceneConfig["graph_label"] + " - " + str(shiftVal),
"y_min" : FirstSqrtSceneConfig["y_min"] - shiftVal,
"y_max" : FirstSqrtSceneConfig["y_max"] - shiftVal,
"show_target_line" : False,
# 0.96 hacked in by checking calculations above
"graph_origin" : 0.96 * shiftVal * UP + FirstSqrtSceneConfig["graph_origin"],
}
# TODO: Pi creatures intrigued
class RewriteEquation(Scene):
def construct(self):
# Can maybe fitz around with smoothening the transform, so just = goes to - and new stuff
# is added at the right end, while things re-center
f_old = TexMobject("f(x)")
f_new = f_old.copy()
equals_old = TexMobject("=")
equals_old_2 = equals_old.copy()
equals_new = equals_old.copy()
g_old = TexMobject("g(x)")
g_new = g_old.copy()
minus_new = TexMobject("-")
zero_new = TexMobject("0")
f_old.next_to(equals_old, LEFT)
g_old.next_to(equals_old, RIGHT)
minus_new.next_to(g_new, LEFT)
f_new.next_to(minus_new, LEFT)
equals_new.next_to(g_new, RIGHT)
zero_new.next_to(equals_new, RIGHT)
self.add(f_old, equals_old, equals_old_2, g_old)
self.wait()
self.play(
ReplacementTransform(f_old, f_new),
ReplacementTransform(equals_old, equals_new),
ReplacementTransform(g_old, g_new),
ReplacementTransform(equals_old_2, minus_new),
ShowCreation(zero_new),
)
self.wait()
class SignsExplanation(Scene):
def construct(self):
num_line = NumberLine(stroke_width = 1)
largest_num = 10
num_line.add_numbers(*range(-largest_num, largest_num + 1))
self.add(num_line)
self.wait()
pos_num = 3
neg_num = -pos_num
pos_arrow = Arrow(
num_line.number_to_point(0),
num_line.number_to_point(pos_num),
buff = 0,
color = positive_color)
neg_arrow = Arrow(
num_line.number_to_point(0),
num_line.number_to_point(neg_num),
buff = 0,
color = negative_color)
#num_line.add_numbers(pos_num)
self.play(ShowCreation(pos_arrow))
#num_line.add_numbers(neg_num)
self.play(ShowCreation(neg_arrow))
class VectorField(Scene):
CONFIG = {
"func" : plane_func_from_complex_func(lambda p : p**2 + 2),
"granularity" : 10,
"arrow_scale_factor" : 0.1,
"normalized_arrow_scale_factor" : 5
}
def construct(self):
num_plane = NumberPlane()
self.add(num_plane)
x_min, y_min = num_plane.point_to_coords(SPACE_WIDTH * LEFT + SPACE_HEIGHT * UP)
x_max, y_max = num_plane.point_to_coords(SPACE_WIDTH * RIGHT + SPACE_HEIGHT * DOWN)
x_points = np.linspace(x_min, x_max, self.granularity)
y_points = np.linspace(y_min, y_max, self.granularity)
points = it.product(x_points, y_points)
sized_arrows = Group()
unsized_arrows = Group()
for (x, y) in points:
output = self.func((x, y))
output_size = np.sqrt(sum(output**2))
normalized_output = output * fdiv(self.normalized_arrow_scale_factor, output_size) # Assume output has nonzero size here
arrow = Vector(output * self.arrow_scale_factor)
normalized_arrow = Vector(normalized_output * self.arrow_scale_factor)
arrow.move_to(num_plane.coords_to_point(x, y))
normalized_arrow.move_to(arrow)
sized_arrows.add(arrow)
unsized_arrows.add(normalized_arrow)
self.add(sized_arrows)
self.wait()
self.play(ReplacementTransform(sized_arrows, unsized_arrows))
self.wait()
class HasItsLimitations(Scene):
def construct(self):
num_line = NumberLine()
num_line.add_numbers()
self.add(num_line)
self.wait()
num_plane = NumberPlane()
num_plane.add_coordinates()
self.play(FadeOut(num_line), FadeIn(num_plane))
self.wait()
complex_plane = ComplexPlane()
complex_plane.add_coordinates()
self.play(FadeOut(num_plane), FadeIn(complex_plane))
class ComplexPlaneIs2d(Scene):
def construct(self):
com_plane = ComplexPlane()
self.add(com_plane)
# TODO: Add labels to axes, specific complex points
self.wait()
class NumberLineScene(Scene):
def construct(self):
num_line = NumberLine()
self.add(num_line)
# TODO: Add labels, arrows, specific points
self.wait()
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)
# TODO: Make this line a thin rectangle
interval_1d = Line(left_point, right_point,
stroke_color = inner_color, stroke_width = stroke_width)
rect_1d = Rectangle(stroke_width = 0, fill_opacity = 1, fill_color = inner_color)
rect_1d.replace(interval_1d)
rect_1d.stretch_to_fit_height(SMALL_BUFF)
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(rect_1d, endpoints_1d)
self.play(ShowCreation(full_1d))
self.wait()
# TODO: Can polish the morphing above; have dots become left and right sides, and
# only then fill in the top and bottom
num_plane = NumberPlane()
random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT]
border_2d = Polygon(
*random_points,
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(full_1d, full_2d))
self.wait()
initial_2d_func = point_func_from_complex_func(lambda c : np.exp(c))
class Initial2dFuncSceneMorphing(Scene):
CONFIG = {
"num_needed_anchor_points" : 10,
"func" : initial_2d_func,
}
def setup(self):
split_line = DashedLine(SPACE_HEIGHT * UP, SPACE_HEIGHT * DOWN)
self.num_plane = NumberPlane(x_radius = SPACE_WIDTH/2)
self.num_plane.to_edge(LEFT, buff = 0)
self.num_plane.prepare_for_nonlinear_transform()
self.add(self.num_plane, split_line)
def squash_onto_left(self, object):
object.shift(SPACE_WIDTH/2 * LEFT)
def squash_onto_right(self, object):
object.shift(SPACE_WIDTH/2 * RIGHT)
def obj_draw(self, input_object):
output_object = input_object.copy()
if input_object.get_num_anchor_points() < self.num_needed_anchor_points:
input_object.insert_n_anchor_points(self.num_needed_anchor_points)
output_object.apply_function(self.func)
self.squash_onto_left(input_object)
self.squash_onto_right(output_object)
self.play(
ShowCreation(input_object),
ShowCreation(output_object)
)
def construct(self):
right_plane = self.num_plane.copy()
right_plane.center()
right_plane.prepare_for_nonlinear_transform()
right_plane.apply_function(self.func)
right_plane.shift(SPACE_WIDTH/2 * RIGHT)
self.right_plane = right_plane
crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1)
crappy_cropper.stretch_to_fit_width(SPACE_WIDTH)
crappy_cropper.to_edge(LEFT, buff = 0)
self.play(
ReplacementTransform(self.num_plane.copy(), right_plane),
FadeIn(crappy_cropper),
Animation(self.num_plane),
run_time = 3
)
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.obj_draw(line)
# Alternative to the above, using MappingCameras, but no morphing animation
class Initial2dFuncSceneWithoutMorphing(Scene):
def setup(self):
left_camera = Camera(**self.camera_config)
right_camera = MappingCamera(
mapping_func = initial_2d_func,
**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.prepare_for_nonlinear_transform()
#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
# TODO: Bunch of Pi walker scenes
# TODO: An odometer scene when introducing winding numbers
# (Just set up an OdometerScene with function matching the walking of the Pi
# creature from previous scene, then place it as a simultaneous inset with Premiere)
class LoopSplitScene(Scene):
CONFIG = {
"output_func" : plane_poly_with_roots((1, 1))
}
def PulsedLine(self,
start, end,
bullet_template,
num_bullets = 4,
pulse_time = 1,
**kwargs):
line = Line(start, end, **kwargs)
anim = LinePulser(
line = line,
bullet_template = bullet_template,
num_bullets = num_bullets,
pulse_time = pulse_time,
output_func = self.output_func,
**kwargs)
return [VGroup(line, *anim.bullets), anim]
def construct(self):
num_plane = NumberPlane(color = LIGHT_GREY, stroke_width = 1)
# We actually don't want to highlight
num_plane.axes.set_stroke(color = WHITE, width = 2)
num_plane.fade()
self.add(num_plane)
scale_factor = 2
shift_term = 0
# Original loop
tl = scale_factor * (UP + LEFT) + shift_term
tm = scale_factor * UP + shift_term
tr = scale_factor * (UP + RIGHT) + shift_term
mr = scale_factor * RIGHT + shift_term
br = scale_factor * (DOWN + RIGHT) + shift_term
bm = scale_factor * DOWN + shift_term
bl = scale_factor * (DOWN + LEFT) + shift_term
lm = scale_factor * LEFT + shift_term
loop_color = BLUE
default_bullet = PiCreature(color = RED)
default_bullet.scale(0.15)
modified_bullet = PiCreature(color = PINK)
modified_bullet.scale(0.15)
def SGroup(*args):
return VGroup(*[arg[0] for arg in args])
top_line = self.PulsedLine(tl, tr, default_bullet, color = BLUE)
right_line = self.PulsedLine(tr, br, modified_bullet, color = BLUE)
bottom_line = self.PulsedLine(br, bl, default_bullet, color = BLUE)
left_line = self.PulsedLine(bl, tl, default_bullet, color = BLUE)
line_list = [top_line, right_line, bottom_line, left_line]
loop = SGroup(*line_list)
for line in line_list:
self.add(*line)
self.wait()
# Splits in middle
split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5))
self.play(ShowCreation(split_line))
self.remove(*split_line)
mid_line_left = self.PulsedLine(tm, bm, default_bullet, color = loop_color)
mid_line_right = self.PulsedLine(bm, tm, modified_bullet, color = loop_color)
self.add(*mid_line_left)
self.add(*mid_line_right)
top_line_left_half = self.PulsedLine(tl, tm, default_bullet, 2, 1, color = loop_color)
top_line_right_half = self.PulsedLine(tm, tr, modified_bullet, 2, 1, color = loop_color)
bottom_line_left_half = self.PulsedLine(bm, bl, default_bullet, 2, 1, color = loop_color)
bottom_line_right_half = self.PulsedLine(br, bm, modified_bullet, 2, 1, color = loop_color)
self.remove(*top_line)
self.add(*top_line_left_half)
self.add(*top_line_right_half)
self.remove(*bottom_line)
self.add(*bottom_line_left_half)
self.add(*bottom_line_right_half)
left_open_loop = SGroup(top_line_left_half, left_line, bottom_line_left_half)
left_closed_loop = VGroup(left_open_loop, mid_line_left[0])
right_open_loop = SGroup(top_line_right_half, right_line, bottom_line_right_half)
right_closed_loop = VGroup(right_open_loop, mid_line_right[0])
# self.play(
# ApplyMethod(left_closed_loop.shift, LEFT),
# ApplyMethod(right_closed_loop.shift, RIGHT)
# )
self.wait()
# self.play(
# ApplyMethod(left_open_loop.shift, LEFT),
# ApplyMethod(right_open_loop.shift, RIGHT)
# )
self.wait()
mid_lines = SGroup(mid_line_left, mid_line_right)
highlight_circle = Circle(color = YELLOW_E) # Perhaps make this a dashed circle?
highlight_circle.surround(mid_lines)
self.play(Indicate(mid_lines), ShowCreation(highlight_circle, run_time = 0.5))
self.wait()
self.play(FadeOut(highlight_circle), FadeOut(mid_lines))
# Because FadeOut didn't remove the continual pulsers, we remove them manually
self.remove(mid_line_left[1], mid_line_right[1])
# Brings loop back together; keep in sync with motions which bring loop apart above
# self.play(
# ApplyMethod(left_open_loop.shift, 2 * RIGHT),
# ApplyMethod(right_open_loop.shift, 2 * LEFT)
# )
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),
**self.camera_config)
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
self.camera = split_screen_camera
# TODO: Perhaps do extra illustration of zooming out and winding around a large circle,
# to illustrate relation between degree and large-scale winding number
class FundThmAlg(EquationSolver2d):
CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)),
"num_iterations" : 5,
"display_in_parallel" : True,
"use_fancy_lines" : False
}
# TODO: Borsuk-Ulam visuals
# Note: May want to do an ordinary square scene, then MappingCamera it into a circle
# class BorsukUlamScene(PiWalker):
# 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
}
class CombineInterval(Scene):
def construct(self):
plus_sign = TexMobject("+", fill_color = positive_color)
minus_sign = TexMobject("-", fill_color = negative_color)
left_point = Dot(LEFT, color = positive_color)
right_point = Dot(RIGHT, color = negative_color)
line1 = Line(LEFT, RIGHT)
interval1 = Group(line1, left_point, right_point)
plus_sign.next_to(left_point, UP)
minus_sign.next_to(right_point, UP)
self.add(interval1, plus_sign, minus_sign)
self.wait()
self.play(
CircleIndicate(plus_sign),
CircleIndicate(minus_sign),
)
self.wait()
mid_point = Dot(ORIGIN, color = GREY)
question_mark = TexMobject("?", fill_color = GREY)
plus_sign_copy = plus_sign.copy()
minus_sign_copy = minus_sign.copy()
new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy)
for sign in new_signs: sign.next_to(mid_point, UP)
self.play(FadeIn(mid_point), FadeIn(question_mark))
self.wait()
self.play(
ApplyMethod(mid_point.set_color, positive_color),
ReplacementTransform(question_mark, plus_sign_copy),
)
self.play(
CircleIndicate(plus_sign_copy),
CircleIndicate(minus_sign),
)
self.wait()
self.play(
ApplyMethod(mid_point.set_color, negative_color),
ReplacementTransform(plus_sign_copy, minus_sign_copy),
)
self.play(
CircleIndicate(minus_sign_copy),
CircleIndicate(plus_sign),
)
self.wait()
class CombineInterval2(Scene):
def construct(self):
plus_sign = TexMobject("+", fill_color = positive_color)
def make_interval(a, b):
line = Line(a, b)
start_dot = Dot(a, color = positive_color)
end_dot = Dot(b, color = positive_color)
start_sign = plus_sign.copy().next_to(start_dot, UP)
end_sign = plus_sign.copy().next_to(end_dot, UP)
return Group(start_sign, end_sign, line, start_dot, end_dot)
def pair_indicate(a, b):
self.play(
CircleIndicate(a),
CircleIndicate(b)
)
left_interval = make_interval(2 * LEFT, LEFT)
right_interval = make_interval(RIGHT, 2 * RIGHT)
self.play(FadeIn(left_interval), FadeIn(right_interval))
pair_indicate(left_interval[0], left_interval[1])
pair_indicate(right_interval[0], right_interval[1])
self.play(
ApplyMethod(left_interval.shift, RIGHT),
ApplyMethod(right_interval.shift, LEFT),
)
pair_indicate(left_interval[0], right_interval[1])
self.wait()
# TODO: Brouwer's fixed point theorem visuals
# class BFTScene(Scene):
# TODO: Pi creatures wide-eyed in amazement
#################
# TODOs, from easiest to hardest:
# Minor fiddling with little things in each animation; placements, colors, timing, text
# Initial odometer scene (simple once previous Pi walker scene is decided upon)
# Writing new Pi walker scenes by parametrizing general template
# Domain coloring scenes by parametrizing general template
# (All the above are basically trivial tinkering at this point)
# ----
# Pi creature emotion stuff
# BFT visuals
# Borsuk-Ulam visuals
# TODO: Add to camera an option for lower-quality (faster-rendered) background than pixel_array,
# helpful for previews
####################
# Random test scenes and test functions go here:
def rect_to_circle((x, y, z)):
size = np.sqrt(x**2 + y**2)
max_abs_size = max(abs(x), abs(y))
return fdiv(np.array((x, y, z)) * max_abs_size, size)
class MapPiWalkerRect(PiWalkerRect):
CONFIG = {
"camera_class" : MappingCamera,
"camera_config" : {"mapping_func" : rect_to_circle},
"display_output_color_map" : True
}
class ShowBack(PiWalkerRect):
CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5))
}
class Diagnostic(Scene):
def construct(self):
testList = map( (lambda n : (n, rev_to_rgba(n))), [0, 0.25, 0.5, 0.9])
print "rev_to_rgbas", testList
self.wait()
# FIN