2019-05-02 20:36:14 -07:00
from manimlib . imports import *
2018-01-09 17:12:28 -08:00
2019-02-16 12:46:54 -08:00
import warnings
warnings . warn ( """
Warning : This file makes use of
ContinualAnimation , which has since
been deprecated
""" )
2018-04-03 20:16:57 +02:00
import time
2018-03-09 10:32:19 -08:00
import mpmath
mpmath . mp . dps = 7
2019-02-11 15:01:38 -08:00
# Warning, this file uses ContinualChangingDecimal,
# which has since been been deprecated. Use a mobject
# updater instead
2018-02-28 19:48:39 -08:00
# Useful constants to play around with
UL = UP + LEFT
UR = UP + RIGHT
DL = DOWN + LEFT
DR = DOWN + RIGHT
standard_rect = np . array ( [ UL , UR , DR , DL ] )
# Used in EquationSolver2d, and a few other places
border_stroke_width = 10
# Used for clockwise circling in some scenes
cw_circle = Circle ( color = WHITE ) . stretch ( - 1 , 0 )
2018-03-05 17:58:57 -08:00
# Used when walker animations are on black backgrounds, in EquationSolver2d and PiWalker
WALKER_LIGHT_COLOR = DARK_GREY
2018-03-20 16:57:06 -07:00
ODOMETER_RADIUS = 1.5
ODOMETER_STROKE_WIDTH = 20
2018-01-17 23:17:42 -08:00
# 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
2018-02-13 16:47:29 -08:00
2018-02-22 12:05:57 -08:00
# This turns counterclockwise revs into their color. Beware, we use CCW angles
# in all display code, but generally think in this video's script in terms of
# CW angles
2018-02-15 11:57:45 -08:00
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 ) )
2018-07-11 11:38:59 -07:00
def point_to_rev ( xxx_todo_changeme6 , allow_origin = False ) :
2018-02-15 11:57:45 -08:00
# 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
2018-07-11 11:38:59 -07:00
( x , y ) = xxx_todo_changeme6
2018-02-15 11:57:45 -08:00
if not ( allow_origin ) and ( x , y ) == ( 0 , 0 ) :
2018-07-11 11:38:59 -07:00
print ( " Error! Angle of (0, 0) computed! " )
2018-02-15 11:57:45 -08:00
return
return fdiv ( np . arctan2 ( y , x ) , TAU )
2018-07-11 11:38:59 -07:00
def point_to_size ( xxx_todo_changeme7 ) :
( x , y ) = xxx_todo_changeme7
2018-02-28 19:48:39 -08:00
return np . sqrt ( x * * 2 + y * * 2 )
# rescaled_size goes from 0 to 1 as size goes from 0 to infinity
# The exact method is arbitrarily chosen to make pleasing color map
# brightness levels
def point_to_rescaled_size ( p ) :
base_size = point_to_size ( p )
return np . sqrt ( fdiv ( base_size , base_size + 1 ) )
2018-02-15 11:57:45 -08:00
def point_to_rgba ( point ) :
rev = point_to_rev ( point , allow_origin = True )
rgba = rev_to_rgba ( rev )
2018-02-28 19:48:39 -08:00
rescaled_size = point_to_rescaled_size ( point )
2018-02-22 12:05:57 -08:00
return rgba * [ rescaled_size , rescaled_size , rescaled_size , 1 ] # Preserve alpha
2018-02-15 11:57:45 -08:00
positive_color = rev_to_color ( 0 )
negative_color = rev_to_color ( 0.5 )
neutral_color = rev_to_color ( 0.25 )
2018-01-11 17:04:10 -08:00
2018-01-29 13:39:56 -08:00
class EquationSolver1d ( GraphScene , ZoomedScene ) :
2018-01-09 17:12:28 -08:00
CONFIG = {
2018-02-13 16:47:29 -08:00
" camera_config " :
{
2018-02-15 11:57:45 -08:00
" use_z_coordinate_for_display_order " : True ,
2018-02-13 16:47:29 -08:00
} ,
2018-01-16 17:48:51 -08:00
" func " : lambda x : x ,
2018-01-10 17:58:20 -08:00
" 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 ,
2018-03-05 17:58:57 -08:00
" show_target_line " : True ,
" base_line_y " : 0 , # The y coordinate at which to draw our x guesses
2018-03-20 16:57:06 -07:00
" show_y_as_deviation " : False , # Displays y-values as deviations from target,
2018-01-09 17:12:28 -08:00
}
2018-01-10 17:58:20 -08:00
def drawGraph ( self ) :
2018-01-09 17:12:28 -08:00
self . setup_axes ( )
2018-01-16 17:48:51 -08:00
self . graph = self . get_graph ( self . func )
2018-01-10 17:58:20 -08:00
self . add ( self . graph )
2018-01-09 17:12:28 -08:00
2018-01-10 17:58:20 -08:00
if self . graph_label != None :
2018-02-12 13:53:33 -08:00
curve_label = self . get_graph_label ( self . graph , self . graph_label ,
x_val = 4 , direction = LEFT )
curve_label . shift ( LEFT )
self . add ( curve_label )
2018-01-09 17:12:28 -08:00
2018-01-10 17:58:20 -08:00
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 ) ,
2019-02-07 15:39:45 -08:00
dash_length = 0.1 )
2018-01-10 17:58:20 -08:00
self . add ( target_line_object )
2018-01-09 17:12:28 -08:00
2018-03-05 17:58:57 -08:00
target_label_num = 0 if self . show_y_as_deviation else self . targetY
target_line_label = TexMobject ( " y = " + str ( target_label_num ) )
2018-01-10 17:58:20 -08:00
target_line_label . next_to ( target_line_object . get_left ( ) , UP + RIGHT )
self . add ( target_line_label )
2018-01-09 17:12:28 -08:00
2018-03-05 17:58:57 -08:00
self . wait ( ) # Give us time to appreciate the graph
if self . show_target_line :
2018-02-12 13:53:33 -08:00
self . play ( FadeOut ( target_line_label ) ) # Reduce clutter
2018-07-11 11:38:59 -07:00
print ( " For reference, graphOrigin: " , self . coords_to_point ( 0 , 0 ) )
print ( " targetYPoint: " , self . coords_to_point ( 0 , self . targetY ) )
2018-02-12 13:53:33 -08:00
2018-02-13 16:47:29 -08:00
# This is a mess right now (first major animation coded),
# but it works; can be refactored later or never
2018-01-10 17:58:20 -08:00
def solveEquation ( self ) :
2018-03-05 17:58:57 -08:00
# Under special conditions, used in GuaranteedZeroScene, we let the
# "lower" guesses actually be too high, or vice versa, and color
# everything accordingly
def color_by_comparison ( val , ref ) :
if val > ref :
return positive_color
elif val < ref :
return negative_color
else :
return neutral_color
lower_color = color_by_comparison ( self . func ( self . initial_lower_x ) , self . targetY )
upper_color = color_by_comparison ( self . func ( self . initial_upper_x ) , self . targetY )
if self . show_y_as_deviation :
y_bias = - self . targetY
else :
y_bias = 0
startBrace = TexMobject ( " | " , stroke_width = 10 ) #TexMobject("[") # Not using [ and ] because they end up crossing over
startBrace . set_color ( lower_color )
endBrace = startBrace . copy ( ) . stretch ( - 1 , 0 )
endBrace . set_color ( upper_color )
2018-02-26 16:07:50 -08:00
genericBraces = Group ( startBrace , endBrace )
#genericBraces.scale(1.5)
leftBrace = startBrace . copy ( )
rightBrace = endBrace . copy ( )
2018-02-06 12:44:38 -08:00
xBraces = Group ( leftBrace , rightBrace )
2018-01-09 17:12:28 -08:00
2018-02-26 16:07:50 -08:00
downBrace = startBrace . copy ( )
upBrace = endBrace . copy ( )
2018-02-06 12:44:38 -08:00
yBraces = Group ( downBrace , upBrace )
2018-01-26 12:26:30 -08:00
yBraces . rotate ( TAU / 4 )
2018-01-09 17:12:28 -08:00
2018-01-10 17:58:20 -08:00
lowerX = self . initial_lower_x
2018-01-16 17:48:51 -08:00
lowerY = self . func ( lowerX )
2018-01-10 17:58:20 -08:00
upperX = self . initial_upper_x
2018-01-16 17:48:51 -08:00
upperY = self . func ( upperX )
2018-01-09 17:12:28 -08:00
2018-03-05 17:58:57 -08:00
leftBrace . move_to ( self . coords_to_point ( lowerX , self . base_line_y ) ) #, aligned_edge = RIGHT)
2018-01-09 17:12:28 -08:00
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 )
2018-03-05 17:58:57 -08:00
rightBrace . move_to ( self . coords_to_point ( upperX , self . base_line_y ) ) #, aligned_edge = LEFT)
2018-01-09 17:12:28 -08:00
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 )
2018-02-12 13:53:33 -08:00
downBrace . move_to ( self . coords_to_point ( 0 , lowerY ) ) #, aligned_edge = UP)
2018-01-09 17:12:28 -08:00
downBraceLabel = DecimalNumber ( lowerY )
downBraceLabel . next_to ( downBrace , LEFT + DOWN , buff = SMALL_BUFF )
downBraceLabelAnimation = ContinualChangingDecimal ( downBraceLabel ,
2018-03-05 17:58:57 -08:00
lambda alpha : self . point_to_coords ( downBrace . get_center ( ) ) [ 1 ] + y_bias ,
2018-01-09 17:12:28 -08:00
tracked_mobject = downBrace )
2018-02-12 13:53:33 -08:00
upBrace . move_to ( self . coords_to_point ( 0 , upperY ) ) #, aligned_edge = DOWN)
2018-01-09 17:12:28 -08:00
upBraceLabel = DecimalNumber ( upperY )
upBraceLabel . next_to ( upBrace , LEFT + UP , buff = SMALL_BUFF )
upBraceLabelAnimation = ContinualChangingDecimal ( upBraceLabel ,
2018-03-05 17:58:57 -08:00
lambda alpha : self . point_to_coords ( upBrace . get_center ( ) ) [ 1 ] + y_bias ,
2018-01-09 17:12:28 -08:00
tracked_mobject = upBrace )
2018-01-10 17:58:20 -08:00
lowerDotPoint = self . input_to_graph_point ( lowerX , self . graph )
2018-03-05 17:58:57 -08:00
lowerDotXPoint = self . coords_to_point ( lowerX , self . base_line_y )
2018-01-16 17:48:51 -08:00
lowerDotYPoint = self . coords_to_point ( 0 , self . func ( lowerX ) )
2018-03-05 17:58:57 -08:00
lowerDot = Dot ( lowerDotPoint + OUT , color = lower_color )
2018-01-10 17:58:20 -08:00
upperDotPoint = self . input_to_graph_point ( upperX , self . graph )
2018-03-05 17:58:57 -08:00
upperDot = Dot ( upperDotPoint + OUT , color = upper_color )
upperDotXPoint = self . coords_to_point ( upperX , self . base_line_y )
2018-01-16 17:48:51 -08:00
upperDotYPoint = self . coords_to_point ( 0 , self . func ( upperX ) )
2018-01-09 17:12:28 -08:00
2018-03-05 17:58:57 -08:00
lowerXLine = Line ( lowerDotXPoint , lowerDotPoint , color = lower_color )
upperXLine = Line ( upperDotXPoint , upperDotPoint , color = upper_color )
2018-03-09 12:07:54 -08:00
lowerYLine = Line ( lowerDotPoint , lowerDotYPoint , color = lower_color )
upperYLine = Line ( upperDotPoint , upperDotYPoint , color = upper_color )
2018-01-09 17:12:28 -08:00
2018-03-05 17:58:57 -08:00
x_guess_line = Line ( lowerDotXPoint , upperDotXPoint , color = WHITE , stroke_width = 10 )
2018-03-09 12:07:54 -08:00
2018-02-15 11:57:45 -08:00
2018-02-13 16:47:29 -08:00
lowerGroup = Group (
lowerDot ,
leftBrace , downBrace ,
2018-02-15 11:57:45 -08:00
lowerXLine , lowerYLine ,
x_guess_line
)
2018-02-13 16:47:29 -08:00
upperGroup = Group (
upperDot ,
rightBrace , upBrace ,
2018-02-15 11:57:45 -08:00
upperXLine , upperYLine ,
x_guess_line
)
2018-02-13 16:47:29 -08:00
2018-03-05 17:58:57 -08:00
initialLowerXDot = Dot ( lowerDotXPoint + OUT , color = lower_color )
initialUpperXDot = Dot ( upperDotXPoint + OUT , color = upper_color )
initialLowerYDot = Dot ( lowerDotYPoint + OUT , color = lower_color )
initialUpperYDot = Dot ( upperDotYPoint + OUT , color = upper_color )
2018-03-09 12:07:54 -08:00
# All the initial adds and ShowCreations are here now:
self . play ( FadeIn ( initialLowerXDot ) , FadeIn ( leftBrace ) , FadeIn ( leftBraceLabel ) )
self . add_foreground_mobjects ( initialLowerXDot , leftBrace )
self . add ( leftBraceLabelAnimation )
self . play ( ShowCreation ( lowerXLine ) )
self . add_foreground_mobject ( lowerDot )
self . play ( ShowCreation ( lowerYLine ) )
self . play ( FadeIn ( initialLowerYDot ) , FadeIn ( downBrace ) , FadeIn ( downBraceLabel ) )
self . add_foreground_mobjects ( initialLowerYDot , downBrace )
self . add ( downBraceLabelAnimation )
self . wait ( )
self . play ( FadeIn ( initialUpperXDot ) , FadeIn ( rightBrace ) , FadeIn ( rightBraceLabel ) )
self . add_foreground_mobjects ( initialUpperXDot , rightBrace )
self . add ( rightBraceLabelAnimation )
self . play ( ShowCreation ( upperXLine ) )
self . add_foreground_mobject ( upperDot )
self . play ( ShowCreation ( upperYLine ) )
self . play ( FadeIn ( initialUpperYDot ) , FadeIn ( upBrace ) , FadeIn ( upBraceLabel ) )
self . add_foreground_mobjects ( initialUpperYDot , upBrace )
self . add ( upBraceLabelAnimation )
self . wait ( )
self . play ( FadeIn ( x_guess_line ) )
self . wait ( )
2018-02-13 16:47:29 -08:00
2018-01-10 17:58:20 -08:00
for i in range ( self . num_iterations ) :
if i == self . iteration_at_which_to_start_zoom :
2018-01-09 17:12:28 -08:00
self . activate_zooming ( )
self . little_rectangle . move_to (
2018-01-10 17:58:20 -08:00
self . coords_to_point ( self . targetX , self . targetY ) )
inverseZoomFactor = 1 / float ( self . zoom_factor )
2018-01-09 17:12:28 -08:00
self . play (
lowerDot . scale_in_place , inverseZoomFactor ,
upperDot . scale_in_place , inverseZoomFactor )
2018-02-15 11:57:45 -08:00
def makeUpdater ( xAtStart , fixed_guess_x ) :
2018-01-09 17:12:28 -08:00
def updater ( group , alpha ) :
2018-02-15 11:57:45 -08:00
dot , xBrace , yBrace , xLine , yLine , guess_line = group
2018-01-09 17:12:28 -08:00
newX = interpolate ( xAtStart , midX , alpha )
2018-01-16 17:48:51 -08:00
newY = self . func ( newX )
2018-01-09 17:12:28 -08:00
graphPoint = self . input_to_graph_point ( newX ,
2018-01-10 17:58:20 -08:00
self . graph )
2018-01-09 17:12:28 -08:00
dot . move_to ( graphPoint )
2018-03-05 17:58:57 -08:00
xAxisPoint = self . coords_to_point ( newX , self . base_line_y )
2018-01-09 17:12:28 -08:00
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 )
2018-03-05 17:58:57 -08:00
fixed_guess_point = self . coords_to_point ( fixed_guess_x , self . base_line_y )
2018-02-15 11:57:45 -08:00
guess_line . put_start_and_end_on ( xAxisPoint , fixed_guess_point )
2018-01-09 17:12:28 -08:00
return group
return updater
midX = ( lowerX + upperX ) / float ( 2 )
2018-01-16 17:48:51 -08:00
midY = self . func ( midX )
2018-03-05 17:58:57 -08:00
# If we run with an interval whose endpoints start off with same sign,
# then nothing after this branching can be trusted to do anything reasonable
# in terms of picking branches or assigning colors
2018-02-15 11:57:45 -08:00
in_negative_branch = midY < self . targetY
sign_color = negative_color if in_negative_branch else positive_color
2018-01-09 17:12:28 -08:00
midCoords = self . coords_to_point ( midX , midY )
2018-02-13 16:47:29 -08:00
midColor = neutral_color
2018-02-15 11:57:45 -08:00
# Hm... even the z buffer isn't helping keep this above x_guess_line
2018-03-05 17:58:57 -08:00
midXBrace = startBrace . copy ( ) # Had start and endBrace been asymmetric, we'd do something different here
midXBrace . set_color ( midColor )
midXBrace . move_to ( self . coords_to_point ( midX , self . base_line_y ) + OUT )
# We only actually add this much later
midXPoint = Dot ( self . coords_to_point ( midX , self . base_line_y ) + OUT , color = sign_color )
2018-02-12 13:53:33 -08:00
x_guess_label_caption = TextMobject ( " New guess: x = " , fill_color = midColor )
x_guess_label_num = DecimalNumber ( midX , fill_color = midColor )
2018-03-30 11:25:37 -07:00
x_guess_label_num . move_to ( 0.9 * FRAME_Y_RADIUS * DOWN )
2018-02-12 13:53:33 -08:00
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 )
2018-02-15 11:57:45 -08:00
y_guess_label_num = DecimalNumber ( midY , fill_color = sign_color )
2018-02-12 13:53:33 -08:00
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 )
2018-01-09 17:12:28 -08:00
self . play (
2018-03-05 17:58:57 -08:00
ReplacementTransform ( leftBrace . copy ( ) , midXBrace ) ,
ReplacementTransform ( rightBrace . copy ( ) , midXBrace ) ,
2018-02-12 13:53:33 -08:00
FadeIn ( x_guess_label ) )
2018-03-05 17:58:57 -08:00
self . add_foreground_mobject ( midXBrace )
midXLine = DashedLine (
self . coords_to_point ( midX , self . base_line_y ) ,
midCoords ,
color = midColor
)
2018-01-09 17:12:28 -08:00
self . play ( ShowCreation ( midXLine ) )
2018-02-15 11:57:45 -08:00
midDot = Dot ( midCoords , color = sign_color )
2018-01-10 17:58:20 -08:00
if ( self . iteration_at_which_to_start_zoom != None and
i > = self . iteration_at_which_to_start_zoom ) :
2018-01-09 17:12:28 -08:00
midDot . scale_in_place ( inverseZoomFactor )
self . add ( midDot )
2018-02-15 11:57:45 -08:00
midYLine = DashedLine ( midCoords , self . coords_to_point ( 0 , midY ) , color = sign_color )
2018-02-13 16:47:29 -08:00
self . play (
ShowCreation ( midYLine ) ,
2018-02-15 11:57:45 -08:00
FadeIn ( y_guess_label ) ,
2018-03-05 17:58:57 -08:00
ApplyMethod ( midXBrace . set_color , sign_color ) ,
ApplyMethod ( midXLine . set_color , sign_color ) ,
run_time = 0.25
)
2018-02-15 11:57:45 -08:00
midYPoint = Dot ( self . coords_to_point ( 0 , midY ) , color = sign_color )
2018-03-05 17:58:57 -08:00
self . add ( midXPoint , midYPoint )
2018-01-09 17:12:28 -08:00
2018-02-15 11:57:45 -08:00
if in_negative_branch :
2018-01-09 17:12:28 -08:00
self . play (
2018-02-15 11:57:45 -08:00
UpdateFromAlphaFunc ( lowerGroup ,
makeUpdater ( lowerX ,
fixed_guess_x = upperX
)
) ,
2018-02-13 16:47:29 -08:00
FadeOut ( guess_labels ) ,
2018-02-15 11:57:45 -08:00
)
2018-01-09 17:12:28 -08:00
lowerX = midX
lowerY = midY
else :
self . play (
2018-02-15 11:57:45 -08:00
UpdateFromAlphaFunc ( upperGroup ,
makeUpdater ( upperX ,
fixed_guess_x = lowerX
)
) ,
2018-02-13 16:47:29 -08:00
FadeOut ( guess_labels ) ,
2018-02-15 11:57:45 -08:00
)
2018-01-09 17:12:28 -08:00
upperX = midX
upperY = midY
2018-02-13 16:47:29 -08:00
#mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected?
2018-03-05 17:58:57 -08:00
self . remove ( midXLine , midDot , midYLine , midXBrace )
2018-01-09 17:12:28 -08:00
2018-01-15 19:15:05 -08:00
self . wait ( )
2018-01-10 17:58:20 -08:00
def construct ( self ) :
self . drawGraph ( )
self . solveEquation ( )
2018-01-16 17:48:51 -08:00
# 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
2018-01-17 23:17:42 -08:00
# TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating
2018-01-16 17:48:51 -08:00
# latter as needed from former?
2018-04-03 20:16:57 +02:00
#
# "cheap" argument only used for diagnostic testing right now
def make_alpha_winder ( func , start , end , num_checkpoints , cheap = False ) :
2018-01-17 23:17:42 -08:00
check_points = [ None for i in range ( num_checkpoints ) ]
check_points [ 0 ] = func ( start )
2018-01-31 17:17:58 -08:00
step_size = fdiv ( end - start , num_checkpoints )
2018-01-17 23:17:42 -08:00
for i in range ( num_checkpoints - 1 ) :
2018-01-16 17:48:51 -08:00
check_points [ i + 1 ] = \
resit_near (
func ( start + ( i + 1 ) * step_size ) ,
check_points [ i ] )
2018-01-17 23:17:42 -08:00
def return_func ( alpha ) :
2018-04-03 20:16:57 +02:00
if cheap :
return alpha # A test to see if this func is responsible for slowdown
2018-06-07 11:50:25 -07:00
index = np . clip ( 0 , num_checkpoints - 1 , int ( alpha * num_checkpoints ) )
2018-01-17 23:17:42 -08:00
x = interpolate ( start , end , alpha )
2018-04-03 20:16:57 +02:00
if cheap :
return check_points [ index ] # A more principled test that at least returns a reasonable answer
else :
return resit_near ( func ( x ) , check_points [ index ] )
2018-01-17 23:17:42 -08:00
return return_func
2018-02-28 19:48:39 -08:00
# The various inconsistent choices of what datatype to use where are a bit of a mess,
# but I'm more keen to rush this video out now than to sort this out.
2018-01-17 23:17:42 -08:00
def complex_to_pair ( c ) :
2018-02-12 13:53:33 -08:00
return np . array ( ( c . real , c . imag ) )
2018-01-17 23:17:42 -08:00
2018-01-19 13:03:12 -08:00
def plane_func_from_complex_func ( f ) :
2018-07-11 11:38:59 -07:00
return lambda x_y4 : complex_to_pair ( f ( complex ( x_y4 [ 0 ] , x_y4 [ 1 ] ) ) )
2018-01-19 13:03:12 -08:00
2018-02-28 19:48:39 -08:00
def point3d_func_from_plane_func ( f ) :
2018-07-11 11:38:59 -07:00
def g ( xxx_todo_changeme ) :
( x , y , z ) = xxx_todo_changeme
2018-02-22 15:32:49 -08:00
f_val = f ( ( x , y ) )
return np . array ( ( f_val [ 0 ] , f_val [ 1 ] , 0 ) )
return g
2018-03-05 17:58:57 -08:00
def point3d_func_from_complex_func ( f ) :
return point3d_func_from_plane_func ( plane_func_from_complex_func ( f ) )
2018-07-11 11:38:59 -07:00
def plane_zeta ( xxx_todo_changeme8 ) :
( x , y ) = xxx_todo_changeme8
2018-03-09 10:32:19 -08:00
CLAMP_SIZE = 1000
2018-03-20 12:57:49 -07:00
z = complex ( x , y )
try :
answer = mpmath . zeta ( z )
except ValueError :
return ( CLAMP_SIZE , 0 )
2018-03-09 10:32:19 -08:00
if abs ( answer ) > CLAMP_SIZE :
answer = answer / abs ( answer ) * CLAMP_SIZE
return ( float ( answer . real ) , float ( answer . imag ) )
2018-07-11 11:38:59 -07:00
def rescaled_plane_zeta ( xxx_todo_changeme9 ) :
( x , y ) = xxx_todo_changeme9
2018-03-30 11:25:37 -07:00
return plane_zeta ( ( x / FRAME_X_RADIUS , 8 * y ) )
2018-03-20 12:57:49 -07:00
2018-02-28 19:48:39 -08:00
# Returns a function from 2-ples to 2-ples
# This function is specified by a list of (x, y, z) tuples,
# and has winding number z (or total of all specified z) around each (x, y)
#
# Can also pass in (x, y) tuples, interpreted as (x, y, 1)
def plane_func_by_wind_spec ( * specs ) :
def embiggen ( p ) :
if len ( p ) == 3 :
return p
elif len ( p ) == 2 :
return ( p [ 0 ] , p [ 1 ] , 1 )
else :
2018-07-11 11:38:59 -07:00
print ( " Error in plane_func_by_wind_spec embiggen! " )
2018-08-09 17:56:05 -07:00
specs = list ( map ( embiggen , specs ) )
2018-02-28 19:48:39 -08:00
2018-08-09 17:56:05 -07:00
pos_specs = [ x_y_z for x_y_z in specs if x_y_z [ 2 ] > 0 ]
neg_specs = [ x_y_z1 for x_y_z1 in specs if x_y_z1 [ 2 ] < 0 ]
2018-02-28 19:48:39 -08:00
2018-08-09 17:56:05 -07:00
neg_specs_made_pos = [ ( x_y_z2 [ 0 ] , x_y_z2 [ 1 ] , - x_y_z2 [ 2 ] ) for x_y_z2 in neg_specs ]
2018-02-28 19:48:39 -08:00
def poly ( c , root_specs ) :
return np . prod ( [ ( c - complex ( x , y ) ) * * z for ( x , y , z ) in root_specs ] )
def complex_func ( c ) :
return poly ( c , pos_specs ) * np . conjugate ( poly ( c , neg_specs_made_pos ) )
return plane_func_from_complex_func ( complex_func )
2018-03-20 16:57:06 -07:00
def scale_func ( func , scale_factor ) :
return lambda x : func ( x ) * scale_factor
2018-02-28 19:48:39 -08:00
# Used in Initial2dFunc scenes, VectorField scene, and ExamplePlaneFunc
2018-03-05 17:58:57 -08:00
example_plane_func_spec = [ ( - 3 , - 1.3 , 2 ) , ( 0.1 , 0.2 , 1 ) , ( 2.8 , - 2 , - 1 ) ]
2018-02-28 19:48:39 -08:00
example_plane_func = plane_func_by_wind_spec ( * example_plane_func_spec )
2018-02-01 16:33:03 -08:00
2018-02-06 12:44:38 -08:00
empty_animation = EmptyAnimation ( )
2018-01-19 13:03:12 -08:00
2018-01-26 12:26:30 -08:00
class WalkerAnimation ( Animation ) :
CONFIG = {
" walk_func " : None , # Must be initialized to use
" remover " : True ,
" rate_func " : None ,
" coords_to_point " : None
}
2018-02-28 19:48:39 -08:00
def __init__ ( self , walk_func , val_func , coords_to_point ,
show_arrows = True , scale_arrows = False ,
* * kwargs ) :
2018-01-26 12:26:30 -08:00
self . walk_func = walk_func
2018-02-28 19:48:39 -08:00
self . val_func = val_func
2018-01-26 12:26:30 -08:00
self . coords_to_point = coords_to_point
self . compound_walker = VGroup ( )
2018-02-06 12:44:38 -08:00
self . show_arrows = show_arrows
2018-02-28 19:48:39 -08:00
self . scale_arrows = scale_arrows
if " walker_stroke_color " in kwargs :
walker_stroke_color = kwargs [ " walker_stroke_color " ]
else :
walker_stroke_color = BLACK
2018-02-06 12:44:38 -08:00
2018-02-28 19:48:39 -08:00
base_walker = Dot ( ) . scale ( 5 * 0.35 ) . set_stroke ( walker_stroke_color , 2 ) # PiCreature().scale(0.8 * 0.35)
self . compound_walker . walker = base_walker
2018-02-01 16:33:03 -08:00
if show_arrows :
2018-02-21 17:12:45 -08:00
self . compound_walker . arrow = Arrow ( ORIGIN , 0.5 * RIGHT , buff = 0 )
self . compound_walker . arrow . match_style ( self . compound_walker . walker )
2018-01-26 12:26:30 -08:00
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
2019-02-08 12:00:51 -08:00
def interpolate_submobject ( self , submobject , starting_submobject , alpha ) :
2018-01-26 12:26:30 -08:00
submobject . points = np . array ( starting_submobject . points )
2019-02-08 11:57:27 -08:00
def interpolate_mobject ( self , alpha ) :
Animation . interpolate_mobject ( self , alpha )
2018-01-26 12:26:30 -08:00
cur_x , cur_y = cur_coords = self . walk_func ( alpha )
2018-01-29 18:13:11 -08:00
cur_point = self . coords_to_point ( cur_x , cur_y )
2018-02-01 16:33:03 -08:00
self . mobject . shift ( cur_point - self . mobject . walker . get_center ( ) )
2018-02-28 19:48:39 -08:00
val = self . val_func ( cur_coords )
rev = point_to_rev ( val )
2018-02-01 16:33:03 -08:00
self . mobject . walker . set_fill ( rev_to_color ( rev ) )
2018-02-06 12:44:38 -08:00
if self . show_arrows :
2018-02-01 16:33:03 -08:00
self . mobject . arrow . set_fill ( rev_to_color ( rev ) )
self . mobject . arrow . rotate (
rev * TAU ,
about_point = self . mobject . arrow . get_start ( )
)
2018-01-26 12:26:30 -08:00
2018-02-28 19:48:39 -08:00
if self . scale_arrows :
size = point_to_rescaled_size ( val )
self . mobject . arrow . scale (
size * 0.3 , # Hack constant; we barely use this feature right now
about_point = self . mobject . arrow . get_start ( )
)
2018-01-29 18:13:11 -08:00
def walker_animation_with_display (
walk_func ,
2018-02-28 19:48:39 -08:00
val_func ,
2018-01-29 18:13:11 -08:00
coords_to_point ,
2018-02-01 16:33:03 -08:00
number_update_func = None ,
show_arrows = True ,
2018-02-28 19:48:39 -08:00
scale_arrows = False ,
2018-05-09 18:21:25 +02:00
num_decimal_places = 1 ,
2018-04-03 20:16:57 +02:00
include_background_rectangle = True ,
2018-01-29 18:13:11 -08:00
* * kwargs
) :
walker_anim = WalkerAnimation (
walk_func = walk_func ,
2018-02-28 19:48:39 -08:00
val_func = val_func ,
2018-01-29 18:13:11 -08:00
coords_to_point = coords_to_point ,
2018-02-01 16:33:03 -08:00
show_arrows = show_arrows ,
2018-02-28 19:48:39 -08:00
scale_arrows = scale_arrows ,
2018-01-29 18:13:11 -08:00
* * kwargs )
walker = walker_anim . compound_walker . walker
if number_update_func != None :
2018-02-01 16:33:03 -08:00
display = DecimalNumber ( 0 ,
2018-05-09 18:21:25 +02:00
num_decimal_places = num_decimal_places ,
2018-04-03 20:16:57 +02:00
fill_color = WHITE if include_background_rectangle else BLACK ,
include_background_rectangle = include_background_rectangle )
if include_background_rectangle :
display . background_rectangle . fill_opacity = 0.5
display . background_rectangle . fill_color = GREY
display . background_rectangle . scale ( 1.2 )
2018-02-01 16:33:03 -08:00
displaycement = 0.5 * DOWN # How about that pun, eh?
2018-04-03 20:16:57 +02:00
# display.move_to(walker.get_center() + displaycement)
display . next_to ( walker , DOWN + RIGHT , SMALL_BUFF )
2018-01-29 18:13:11 -08:00
display_anim = ChangingDecimal ( display ,
number_update_func ,
tracked_mobject = walker_anim . compound_walker . walker ,
* * kwargs )
2019-02-05 15:39:58 -08:00
anim_group = AnimationGroup ( walker_anim , display_anim , rate_func = linear )
2018-01-29 18:13:11 -08:00
return anim_group
else :
return walker_anim
def LinearWalker (
start_coords ,
end_coords ,
coords_to_point ,
2018-02-28 19:48:39 -08:00
val_func ,
2018-01-29 18:13:11 -08:00
number_update_func = None ,
2018-02-01 16:33:03 -08:00
show_arrows = True ,
2018-02-28 19:48:39 -08:00
scale_arrows = False ,
2018-04-03 20:16:57 +02:00
include_background_rectangle = True ,
2018-01-29 18:13:11 -08:00
* * kwargs
) :
2018-01-26 12:26:30 -08:00
walk_func = lambda alpha : interpolate ( start_coords , end_coords , alpha )
2018-01-29 18:13:11 -08:00
return walker_animation_with_display (
2018-01-26 12:26:30 -08:00
walk_func = walk_func ,
coords_to_point = coords_to_point ,
2018-02-28 19:48:39 -08:00
val_func = val_func ,
2018-01-29 18:13:11 -08:00
number_update_func = number_update_func ,
2018-02-01 16:33:03 -08:00
show_arrows = show_arrows ,
2018-02-28 19:48:39 -08:00
scale_arrows = scale_arrows ,
2018-04-03 20:16:57 +02:00
include_background_rectangle = include_background_rectangle ,
2018-01-26 12:26:30 -08:00
* * kwargs )
2018-01-31 17:17:58 -08:00
class ColorMappedByFuncScene ( Scene ) :
CONFIG = {
2018-02-01 16:33:03 -08:00
" func " : lambda p : p ,
" num_plane " : NumberPlane ( ) ,
2018-02-06 12:44:38 -08:00
" show_num_plane " : True ,
2018-03-05 17:58:57 -08:00
" show_output " : False ,
" hide_background " : False #Background used for color mapped objects, not as background
2018-01-31 17:17:58 -08:00
}
2018-03-06 13:56:42 -08:00
def short_path_to_long_path ( self , filename_with_ext ) :
2018-08-11 23:34:34 -07:00
return self . get_image_file_path ( filename_with_ext )
2018-03-06 13:56:42 -08:00
2018-02-06 12:44:38 -08:00
def setup ( self ) :
2018-02-22 15:32:49 -08:00
# The composition of input_to_pos and pos_to_color
# is to be equal to func (which turns inputs into colors)
# However, depending on whether we are showing input or output (via a MappingCamera),
# we color the background using either func or the identity map
2018-02-06 12:44:38 -08:00
if self . show_output :
2018-02-15 14:44:32 -08:00
self . input_to_pos_func = self . func
self . pos_to_color_func = lambda p : p
2018-02-06 12:44:38 -08:00
else :
2018-02-15 14:44:32 -08:00
self . input_to_pos_func = lambda p : p
self . pos_to_color_func = self . func
2018-07-11 11:38:59 -07:00
self . pixel_pos_to_color_func = lambda x_y3 : self . pos_to_color_func (
self . num_plane . point_to_coords_cheap ( np . array ( [ x_y3 [ 0 ] , x_y3 [ 1 ] , 0 ] ) )
2018-03-09 10:32:19 -08:00
)
2018-02-22 12:05:57 -08:00
jitter_val = 0.1
line_coords = np . linspace ( - 10 , 10 ) + jitter_val
func_hash_points = it . product ( line_coords , line_coords )
2018-08-11 23:34:34 -07:00
2018-02-22 12:05:57 -08:00
def mini_hasher ( p ) :
2018-03-09 10:32:19 -08:00
rgba = point_to_rgba ( self . pixel_pos_to_color_func ( p ) )
2018-02-22 15:32:49 -08:00
if rgba [ 3 ] != 1.0 :
2018-07-11 11:38:59 -07:00
print ( " Warning! point_to_rgba assigns fractional alpha " , rgba [ 3 ] )
2018-02-22 15:32:49 -08:00
return tuple ( rgba )
2018-08-11 23:34:34 -07:00
2018-02-22 12:05:57 -08:00
to_hash = tuple ( mini_hasher ( p ) for p in func_hash_points )
2018-02-15 14:44:32 -08:00
func_hash = hash ( to_hash )
2018-02-22 15:32:49 -08:00
# We hash just based on output image
# Thus, multiple scenes with same output image can re-use it
# without recomputation
2018-05-21 12:11:46 -07:00
full_hash = hash ( ( func_hash , self . camera . get_pixel_width ( ) ) )
2018-03-06 13:56:42 -08:00
self . background_image_file = self . short_path_to_long_path (
2018-02-26 16:07:50 -08:00
" color_mapped_bg_hash_ " + str ( full_hash ) + " .png "
2018-02-19 14:32:46 -08:00
)
self . in_background_pass = not os . path . exists ( self . background_image_file )
2018-07-11 11:38:59 -07:00
print ( " Background file: " + self . background_image_file )
2018-02-15 14:44:32 -08:00
if self . in_background_pass :
2018-07-11 11:38:59 -07:00
print ( " The background file does not exist yet; this will be a background creation + video pass " )
2018-02-15 14:44:32 -08:00
else :
2018-07-11 11:38:59 -07:00
print ( " The background file already exists; this will only be a video pass " )
2018-02-06 12:44:38 -08:00
2018-01-31 17:17:58 -08:00
def construct ( self ) :
2018-02-15 14:44:32 -08:00
if self . in_background_pass :
self . camera . set_background_from_func (
2018-07-11 11:38:59 -07:00
lambda x_y : point_to_rgba (
2018-03-09 10:32:19 -08:00
self . pixel_pos_to_color_func (
2018-07-11 11:38:59 -07:00
( x_y [ 0 ] , x_y [ 1 ] )
2018-02-15 14:44:32 -08:00
)
2018-01-31 17:17:58 -08:00
)
)
2018-08-11 23:34:34 -07:00
self . save_image ( self . background_image_file , mode = " RGBA " )
2018-02-15 14:44:32 -08:00
2018-03-05 17:58:57 -08:00
if self . hide_background :
# Clearing background
self . camera . background_image = None
else :
# Even if we just computed the background, we switch to the file now
self . camera . background_image = self . background_image_file
2018-02-19 14:32:46 -08:00
self . camera . init_background ( )
if self . show_num_plane :
self . num_plane . fade ( )
self . add ( self . num_plane )
2018-01-31 17:17:58 -08:00
2018-02-15 11:57:45 -08:00
class PureColorMap ( ColorMappedByFuncScene ) :
CONFIG = {
" show_num_plane " : False
}
2018-02-06 12:44:38 -08:00
def construct ( self ) :
ColorMappedByFuncScene . construct ( self )
self . wait ( )
2018-02-22 12:05:57 -08:00
# This sets self.background_image_file, but does not display it as the background
class ColorMappedObjectsScene ( ColorMappedByFuncScene ) :
CONFIG = {
2018-03-05 17:58:57 -08:00
" show_num_plane " : False ,
" hide_background " : True ,
2018-02-22 12:05:57 -08:00
}
2018-01-31 17:17:58 -08:00
class PiWalker ( ColorMappedByFuncScene ) :
2018-01-26 12:26:30 -08:00
CONFIG = {
" walk_coords " : [ ] ,
2018-02-01 16:33:03 -08:00
" step_run_time " : 1 ,
2018-02-28 19:48:39 -08:00
" scale_arrows " : False ,
2018-03-20 16:57:06 -07:00
" display_wind " : True ,
2018-03-22 11:54:08 -07:00
" wind_reset_indices " : [ ] ,
2018-02-28 19:48:39 -08:00
" display_size " : False ,
" display_odometer " : False ,
" color_foreground_not_background " : False ,
2018-03-20 16:57:06 -07:00
" show_num_plane " : False ,
" draw_lines " : True ,
" num_checkpoints " : 10 ,
2018-05-09 18:21:25 +02:00
" num_decimal_places " : 1 ,
2018-04-03 20:16:57 +02:00
" include_background_rectangle " : False ,
2018-01-26 12:26:30 -08:00
}
def construct ( self ) :
2018-01-31 17:17:58 -08:00
ColorMappedByFuncScene . construct ( self )
2018-01-26 12:26:30 -08:00
2018-02-28 19:48:39 -08:00
if self . color_foreground_not_background or self . display_odometer :
# Clear background
self . camera . background_image = None
self . camera . init_background ( )
num_plane = self . num_plane
2018-01-26 12:26:30 -08:00
walk_coords = self . walk_coords
2018-02-28 19:48:39 -08:00
points = [ num_plane . coords_to_point ( x , y ) for x , y in walk_coords ]
polygon = Polygon ( * points , color = WHITE )
if self . color_foreground_not_background :
polygon . stroke_width = border_stroke_width
polygon . color_using_background_image ( self . background_image_file )
total_run_time = len ( points ) * self . step_run_time
2019-02-05 15:39:58 -08:00
polygon_anim = ShowCreation ( polygon , run_time = total_run_time , rate_func = linear )
2018-03-20 16:57:06 -07:00
walker_anim = empty_animation
2018-02-28 19:48:39 -08:00
2018-03-20 16:57:06 -07:00
start_wind = 0
2018-01-26 12:26:30 -08:00
for i in range ( len ( walk_coords ) ) :
2018-02-28 19:48:39 -08:00
start_coords = walk_coords [ i ]
end_coords = walk_coords [ ( i + 1 ) % len ( walk_coords ) ]
# We need to do this roundabout default argument thing to get the closure we want,
# so the next iteration changing start_coords, end_coords doesn't change this closure
val_alpha_func = lambda a , start_coords = start_coords , end_coords = end_coords : self . func ( interpolate ( start_coords , end_coords , a ) )
2018-03-20 16:57:06 -07:00
if self . display_wind :
clockwise_val_func = lambda p : - point_to_rev ( self . func ( p ) )
alpha_winder = make_alpha_winder ( clockwise_val_func , start_coords , end_coords , self . num_checkpoints )
number_update_func = lambda alpha , alpha_winder = alpha_winder , start_wind = start_wind : alpha_winder ( alpha ) - alpha_winder ( 0 ) + start_wind
2018-03-22 11:54:08 -07:00
start_wind = 0 if i + 1 in self . wind_reset_indices else number_update_func ( 1 )
2018-03-20 16:57:06 -07:00
elif self . display_size :
2018-02-28 19:48:39 -08:00
# We need to do this roundabout default argument thing to get the closure we want,
# so the next iteration changing val_alpha_func doesn't change this closure
number_update_func = lambda a , val_alpha_func = val_alpha_func : point_to_rescaled_size ( val_alpha_func ( a ) ) # We only use this for diagnostics
else :
number_update_func = None
new_anim = LinearWalker (
start_coords = start_coords ,
end_coords = end_coords ,
coords_to_point = num_plane . coords_to_point ,
val_func = self . func ,
remover = ( i < len ( walk_coords ) - 1 ) ,
show_arrows = not self . show_output ,
scale_arrows = self . scale_arrows ,
number_update_func = number_update_func ,
run_time = self . step_run_time ,
2018-03-22 11:54:08 -07:00
walker_stroke_color = WALKER_LIGHT_COLOR if self . color_foreground_not_background else BLACK ,
2018-05-09 18:21:25 +02:00
num_decimal_places = self . num_decimal_places ,
2018-04-03 20:16:57 +02:00
include_background_rectangle = self . include_background_rectangle ,
2018-02-28 19:48:39 -08:00
)
if self . display_odometer :
# Discard above animation and show an odometer instead
# We need to do this roundabout default argument thing to get the closure we want,
# so the next iteration changing val_alpha_func doesn't change this closure
rev_func = lambda a , val_alpha_func = val_alpha_func : point_to_rev ( val_alpha_func ( a ) )
base_arrow = Arrow ( ORIGIN , RIGHT , buff = 0 )
new_anim = FuncRotater ( base_arrow ,
2018-01-26 12:26:30 -08:00
rev_func = rev_func ,
2018-02-28 19:48:39 -08:00
run_time = self . step_run_time ,
2019-02-05 15:39:58 -08:00
rate_func = linear ,
2018-02-28 19:48:39 -08:00
remover = i < len ( walk_coords ) - 1 ,
)
walker_anim = Succession ( walker_anim , new_anim )
2018-01-29 13:39:56 -08:00
2018-01-30 13:55:59 -08:00
# TODO: Allow smooth paths instead of breaking them up into lines, and
2018-01-29 13:39:56 -08:00
# use point_from_proportion to get points along the way
2018-01-26 12:26:30 -08:00
2018-02-28 19:48:39 -08:00
if self . display_odometer :
2018-03-20 16:57:06 -07:00
color_wheel = Circle ( radius = ODOMETER_RADIUS )
color_wheel . stroke_width = ODOMETER_STROKE_WIDTH
color_wheel . color_using_background_image ( self . short_path_to_long_path ( " pure_color_map.png " ) ) # Manually inserted here; this is unclean
self . add ( color_wheel )
2018-02-28 19:48:39 -08:00
self . play ( walker_anim )
else :
2018-03-20 16:57:06 -07:00
if self . draw_lines :
self . play ( polygon_anim , walker_anim )
else :
# (Note: Turns out, play is unhappy playing empty_animation, as had been
# previous approach to this toggle; should fix that)
self . play ( walker_anim )
2018-02-28 19:48:39 -08:00
2018-01-26 12:26:30 -08:00
self . wait ( )
class PiWalkerRect ( PiWalker ) :
CONFIG = {
" start_x " : - 1 ,
" start_y " : 1 ,
" walk_width " : 2 ,
" walk_height " : 2 ,
2018-03-20 16:57:06 -07:00
" func " : plane_func_from_complex_func ( lambda c : c * * 2 ) ,
" double_up " : False ,
2018-03-22 11:54:08 -07:00
# New default for the scenes using this:
" display_wind " : True
2018-01-26 12:26:30 -08:00
}
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 ]
2018-03-20 16:57:06 -07:00
if self . double_up :
self . walk_coords = self . walk_coords + self . walk_coords
2018-01-26 12:26:30 -08:00
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 )
2018-07-11 11:38:59 -07:00
def split_interval ( xxx_todo_changeme10 ) :
( a , b ) = xxx_todo_changeme10
2018-03-22 11:54:08 -07:00
mid = ( a + b ) / 2.0
return ( ( a , mid ) , ( mid , b ) )
# I am surely reinventing some wheel here, but what's done is done...
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 get_center ( self ) :
return interpolate ( self . get_top_left ( ) , self . get_bottom_right ( ) , 0.5 )
def get_width ( self ) :
return self . rect [ 0 ] [ 1 ] - self . rect [ 0 ] [ 0 ]
def get_height ( self ) :
return self . rect [ 1 ] [ 1 ] - self . rect [ 1 ] [ 0 ]
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 :
2018-07-11 11:38:59 -07:00
print ( " RectangleData.splits_on_dim passed illegitimate dimension! " )
2018-03-22 11:54:08 -07:00
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 :
2018-07-11 11:38:59 -07:00
print ( " RectangleData.split_line_on_dim passed illegitimate dimension! " )
2018-03-22 11:54:08 -07:00
return tuple ( [ mid ( x , y ) for ( x , y ) in sides ] )
2018-03-20 17:19:29 -07:00
class EquationSolver2dNode ( object ) :
def __init__ ( self , first_anim , children = [ ] ) :
self . first_anim = first_anim
self . children = children
2018-03-20 17:35:57 -07:00
def depth ( self ) :
if len ( self . children ) == 0 :
return 0
2018-08-09 17:56:05 -07:00
return 1 + max ( [ n . depth ( ) for n in self . children ] )
2018-03-20 17:35:57 -07:00
def nodes_at_depth ( self , n ) :
if n == 0 :
return [ self ]
2018-03-22 11:54:08 -07:00
# Not the efficient way to flatten lists, because Python + is linear in list size,
# but we have at most two children, so no big deal here
2018-08-09 17:56:05 -07:00
return sum ( [ c . nodes_at_depth ( n - 1 ) for c in self . children ] , [ ] )
2018-03-20 17:35:57 -07:00
# This is definitely NOT the efficient way to do BFS, but I'm just trying to write something
# quick without thinking that gets the job done on small instances for now
def hacky_bfs ( self ) :
depth = self . depth ( )
2018-03-22 11:54:08 -07:00
# Not the efficient way to flatten lists, because Python + is linear in list size,
# but this IS hacky_bfs...
2018-08-09 17:56:05 -07:00
return sum ( [ self . nodes_at_depth ( i ) for i in range ( depth + 1 ) ] , [ ] )
2018-03-20 17:35:57 -07:00
2018-03-20 17:19:29 -07:00
def display_in_series ( self ) :
2018-08-09 17:56:05 -07:00
return Succession ( self . first_anim , * [ n . display_in_series ( ) for n in self . children ] )
2018-03-20 17:19:29 -07:00
def display_in_parallel ( self ) :
2018-08-09 17:56:05 -07:00
return Succession ( self . first_anim , AnimationGroup ( * [ n . display_in_parallel ( ) for n in self . children ] ) )
2018-03-20 17:19:29 -07:00
def display_in_bfs ( self ) :
2018-03-20 17:35:57 -07:00
bfs_nodes = self . hacky_bfs ( )
2018-08-09 17:56:05 -07:00
return Succession ( * [ n . first_anim for n in bfs_nodes ] )
2018-03-20 17:19:29 -07:00
2018-04-03 20:16:57 +02:00
def play_in_bfs ( self , scene , border_anim ) :
bfs_nodes = self . hacky_bfs ( )
2018-07-11 11:38:59 -07:00
print ( " Number of nodes: " , len ( bfs_nodes ) )
2018-04-03 20:16:57 +02:00
if len ( bfs_nodes ) < 1 :
2018-07-11 11:38:59 -07:00
print ( " Less than 1 node! Aborting! " )
2018-04-03 20:16:57 +02:00
return
scene . play ( bfs_nodes [ 0 ] . first_anim , border_anim )
for node in bfs_nodes [ 1 : ] :
scene . play ( node . first_anim )
2018-02-22 12:05:57 -08:00
class EquationSolver2d ( ColorMappedObjectsScene ) :
2018-01-17 23:17:42 -08:00
CONFIG = {
2018-02-13 16:47:29 -08:00
" camera_config " : { " use_z_coordinate_for_display_order " : True } ,
2018-03-06 13:56:42 -08:00
" initial_lower_x " : - 5 ,
" initial_upper_x " : 5 ,
" initial_lower_y " : - 3 ,
" initial_upper_y " : 3 ,
2018-04-03 20:16:57 +02:00
" num_iterations " : 0 ,
2018-01-19 13:03:12 -08:00
" num_checkpoints " : 10 ,
2018-03-20 17:19:29 -07:00
# Should really merge this into one enum-style variable
2018-03-09 10:32:19 -08:00
" display_in_parallel " : False ,
2018-03-20 17:19:29 -07:00
" display_in_bfs " : False ,
2018-02-06 12:44:38 -08:00
" use_fancy_lines " : True ,
2018-04-03 20:16:57 +02:00
" line_color " : WHITE , # Only used for non-fancy lines
2018-01-19 13:03:12 -08:00
# 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
2018-02-28 19:48:39 -08:00
# Walker settings
" show_arrows " : True ,
" scale_arrows " : False ,
# Special case settings
# These are used to hack UhOhScene, where we display different colors than
# are actually, secretly, guiding the evolution of the EquationSolver2d
#
# replacement_background_image_file has to be manually configured
" show_winding_numbers " : True ,
2018-03-06 13:56:42 -08:00
# Used for UhOhScene;
2018-03-22 11:54:08 -07:00
" manual_wind_override " : None ,
2018-04-03 20:16:57 +02:00
" show_cursor " : True ,
" linger_parameter " : 0.5 ,
2018-03-22 15:02:20 -07:00
2018-04-03 20:16:57 +02:00
" use_separate_plays " : False ,
" use_cheap_winding_numbers " : False , # To use this, make num_checkpoints large
2018-01-17 23:17:42 -08:00
}
def construct ( self ) :
2018-04-03 20:16:57 +02:00
if self . num_iterations == 0 :
2018-07-11 11:38:59 -07:00
print ( " You forgot to set num_iterations (maybe you meant to subclass something other than EquationSolver2d directly?) " )
2018-04-03 20:16:57 +02:00
return
2018-02-22 12:05:57 -08:00
ColorMappedObjectsScene . construct ( self )
2018-01-31 17:17:58 -08:00
num_plane = self . num_plane
2018-01-17 23:17:42 -08:00
2018-02-28 19:48:39 -08:00
clockwise_val_func = lambda p : - point_to_rev ( self . func ( p ) )
2018-01-17 23:17:42 -08:00
2018-04-03 20:16:57 +02:00
base_line = Line ( UP , RIGHT , stroke_width = border_stroke_width , color = self . line_color )
2018-02-20 21:54:40 -08:00
2018-03-06 13:56:42 -08:00
if self . use_fancy_lines :
base_line . color_using_background_image ( self . background_image_file )
def match_style_with_bg ( obj1 , obj2 ) :
obj1 . match_style ( obj2 )
bg = obj2 . get_background_image_file ( )
if bg != None :
obj1 . color_using_background_image ( bg )
2018-02-20 21:54:40 -08:00
run_time_base = 1
2018-03-22 15:02:20 -07:00
run_time_with_lingering = run_time_base + self . linger_parameter
2018-02-20 21:54:40 -08:00
base_rate = lambda t : t
linger_rate = squish_rate_func ( lambda t : t , 0 ,
fdiv ( run_time_base , run_time_with_lingering ) )
2018-03-22 11:54:08 -07:00
cursor_base = TextMobject ( " ? " )
cursor_base . scale ( 2 )
2018-03-06 13:56:42 -08:00
# Helper functions for manual_wind_override
def head ( m ) :
if m == None :
return None
return m [ 0 ]
def child ( m , i ) :
if m == None or m == 0 :
return None
return m [ i + 1 ]
def Animate2dSolver ( cur_depth , rect , dim_to_split ,
sides_to_draw = [ 0 , 1 , 2 , 3 ] ,
manual_wind_override = None ) :
2018-07-11 11:38:59 -07:00
print ( " Solver at depth: " + str ( cur_depth ) )
2018-01-29 18:13:11 -08:00
2018-01-19 13:03:12 -08:00
if cur_depth > = self . num_iterations :
2018-03-20 17:19:29 -07:00
return EquationSolver2dNode ( empty_animation )
2018-01-19 13:03:12 -08:00
2018-02-06 12:44:38 -08:00
def draw_line_return_wind ( start , end , start_wind , should_linger = False , draw_line = True ) :
2018-04-03 20:16:57 +02:00
alpha_winder = make_alpha_winder ( clockwise_val_func , start , end , self . num_checkpoints , cheap = self . use_cheap_winding_numbers )
2018-01-19 13:03:12 -08:00
a0 = alpha_winder ( 0 )
rebased_winder = lambda alpha : alpha_winder ( alpha ) - a0 + start_wind
2018-02-21 17:12:45 -08:00
colored_line = Line ( num_plane . coords_to_point ( * start ) + IN , num_plane . coords_to_point ( * end ) + IN )
2018-03-06 13:56:42 -08:00
match_style_with_bg ( colored_line , base_line )
2018-02-01 16:33:03 -08:00
2018-01-26 12:26:30 -08:00
walker_anim = LinearWalker (
start_coords = start ,
end_coords = end ,
coords_to_point = num_plane . coords_to_point ,
2018-03-06 13:56:42 -08:00
val_func = self . func , # Note: This is the image func, and not logic_func
2018-02-28 19:48:39 -08:00
number_update_func = rebased_winder if self . show_winding_numbers else None ,
remover = True ,
2018-03-05 17:58:57 -08:00
walker_stroke_color = WALKER_LIGHT_COLOR ,
2018-02-28 19:48:39 -08:00
show_arrows = self . show_arrows ,
scale_arrows = self . scale_arrows ,
2018-01-26 12:26:30 -08:00
)
2018-02-20 21:54:40 -08:00
2018-02-01 16:33:03 -08:00
if should_linger : # Do we need an "and not self.display_in_parallel" here?
2018-02-20 21:54:40 -08:00
run_time = run_time_with_lingering
rate_func = linger_rate
2018-02-01 16:33:03 -08:00
else :
2018-02-20 21:54:40 -08:00
run_time = run_time_base
rate_func = base_rate
2018-02-01 16:33:03 -08:00
2018-02-06 12:44:38 -08:00
opt_line_anim = ShowCreation ( colored_line ) if draw_line else empty_animation
2018-01-29 13:39:56 -08:00
line_draw_anim = AnimationGroup (
2018-02-06 12:44:38 -08:00
opt_line_anim ,
2018-01-29 13:39:56 -08:00
walker_anim ,
2018-02-20 21:54:40 -08:00
run_time = run_time ,
2018-02-01 16:33:03 -08:00
rate_func = rate_func )
return ( line_draw_anim , rebased_winder ( 1 ) )
2018-01-17 23:17:42 -08:00
2018-01-18 11:51:09 -08:00
wind_so_far = 0
2018-02-06 12:44:38 -08:00
anim = empty_animation
2018-01-18 11:51:09 -08:00
sides = [
2018-01-19 13:03:12 -08:00
rect . get_top ( ) ,
rect . get_right ( ) ,
rect . get_bottom ( ) ,
rect . get_left ( )
2018-01-18 11:51:09 -08:00
]
2018-02-01 16:33:03 -08:00
for ( i , ( start , end ) ) in enumerate ( sides ) :
( next_anim , wind_so_far ) = draw_line_return_wind ( start , end , wind_so_far ,
2018-02-06 12:44:38 -08:00
should_linger = i == len ( sides ) - 1 ,
draw_line = i in sides_to_draw )
2018-01-19 13:03:12 -08:00
anim = Succession ( anim , next_anim )
2018-01-17 23:17:42 -08:00
2018-03-22 11:54:08 -07:00
if self . show_cursor :
cursor = cursor_base . copy ( )
center_x , center_y = rect . get_center ( )
width = rect . get_width ( )
height = rect . get_height ( )
2018-04-03 20:16:57 +02:00
cursor . move_to ( num_plane . coords_to_point ( center_x , center_y ) + 10 * IN )
2018-03-22 11:54:08 -07:00
cursor . scale ( min ( width , height ) )
# Do a quick FadeIn, wait, and quick FadeOut on the cursor, matching rectangle-drawing time
cursor_anim = Succession (
FadeIn ( cursor , run_time = 0.1 ) ,
Animation ( cursor , run_time = 3.8 ) ,
FadeOut ( cursor , run_time = 0.1 )
)
anim = AnimationGroup ( anim , cursor_anim )
2018-04-03 20:16:57 +02:00
override_wind = head ( manual_wind_override )
if override_wind != None :
total_wind = override_wind
else :
total_wind = round ( wind_so_far )
2018-01-17 23:17:42 -08:00
if total_wind == 0 :
2018-01-19 13:03:12 -08:00
coords = [
rect . get_top_left ( ) ,
rect . get_top_right ( ) ,
rect . get_bottom_right ( ) ,
rect . get_bottom_left ( )
]
2018-02-21 17:12:45 -08:00
points = np . array ( [ num_plane . coords_to_point ( x , y ) for ( x , y ) in coords ] ) + 3 * IN
2018-01-26 12:26:30 -08:00
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating
# their "Nothing here" status?
2018-02-06 12:44:38 -08:00
# Or draw a large X or something
2018-02-28 19:48:39 -08:00
fill_rect = polygonObject = Polygon ( * points , fill_opacity = 0.8 , color = DARK_GREY )
2018-03-20 17:19:29 -07:00
return EquationSolver2dNode ( Succession ( anim , FadeIn ( fill_rect ) ) )
2018-01-17 23:17:42 -08:00
else :
2018-01-19 13:03:12 -08:00
( sub_rect1 , sub_rect2 ) = rect . splits_on_dim ( dim_to_split )
2018-02-06 12:44:38 -08:00
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 ) ]
2018-03-20 17:19:29 -07:00
children = [
2018-01-19 13:03:12 -08:00
Animate2dSolver (
cur_depth = cur_depth + 1 ,
rect = sub_rect ,
2018-02-06 12:44:38 -08:00
dim_to_split = 1 - dim_to_split ,
2018-03-06 13:56:42 -08:00
sides_to_draw = [ side_to_draw ] ,
manual_wind_override = child ( manual_wind_override , index )
2018-01-19 13:03:12 -08:00
)
2018-03-06 13:56:42 -08:00
for ( index , ( sub_rect , side_to_draw ) ) in enumerate ( sub_rect_and_sides )
2018-01-19 13:03:12 -08:00
]
mid_line_coords = rect . split_line_on_dim ( dim_to_split )
2018-02-21 17:12:45 -08:00
mid_line_points = [ num_plane . coords_to_point ( x , y ) + 2 * IN for ( x , y ) in mid_line_coords ]
2018-02-06 12:44:38 -08:00
mid_line = DashedLine ( * mid_line_points )
2018-03-20 17:19:29 -07:00
return EquationSolver2dNode ( Succession ( anim , ShowCreation ( mid_line ) ) , children )
2018-01-19 13:03:12 -08:00
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 )
2018-07-11 11:38:59 -07:00
print ( " Starting to compute anim " )
2018-01-29 18:13:11 -08:00
2018-03-20 17:19:29 -07:00
node = Animate2dSolver (
2018-01-19 13:03:12 -08:00
cur_depth = 0 ,
rect = rect ,
dim_to_split = 0 ,
2018-03-06 13:56:42 -08:00
sides_to_draw = [ ] ,
manual_wind_override = self . manual_wind_override
2018-01-19 13:03:12 -08:00
)
2018-01-17 23:17:42 -08:00
2018-07-11 11:38:59 -07:00
print ( " Done computing anim " )
2018-01-29 18:13:11 -08:00
2018-03-20 17:19:29 -07:00
if self . display_in_parallel :
anim = node . display_in_parallel ( )
elif self . display_in_bfs :
anim = node . display_in_bfs ( )
else :
anim = node . display_in_series ( )
2018-02-20 21:54:40 -08:00
# Keep timing details here in sync with details above
rect_points = [
rect . get_top_left ( ) ,
rect . get_top_right ( ) ,
rect . get_bottom_right ( ) ,
rect . get_bottom_left ( ) ,
]
2018-08-09 17:56:05 -07:00
border = Polygon ( * [ num_plane . coords_to_point ( * x ) + IN for x in rect_points ] )
2018-03-06 13:56:42 -08:00
match_style_with_bg ( border , base_line )
2018-02-20 21:54:40 -08:00
rect_time_without_linger = 4 * run_time_base
rect_time_with_linger = 3 * run_time_base + run_time_with_lingering
def rect_rate ( alpha ) :
time_in = alpha * rect_time_with_linger
if time_in < 3 * run_time_base :
return fdiv ( time_in , 4 * run_time_base )
else :
time_in_last_leg = time_in - 3 * run_time_base
alpha_in_last_leg = fdiv ( time_in_last_leg , run_time_with_lingering )
return interpolate ( 0.75 , 1 , linger_rate ( alpha_in_last_leg ) )
border_anim = ShowCreation (
border ,
run_time = rect_time_with_linger ,
rate_func = rect_rate
)
2018-07-11 11:38:59 -07:00
print ( " About to do the big Play; for reference, the current time is " , time . strftime ( " % H: % M: % S " ) )
2018-03-23 14:40:10 -07:00
2018-04-03 20:16:57 +02:00
if self . use_separate_plays :
node . play_in_bfs ( self , border_anim )
else :
self . play ( anim , border_anim )
2018-03-22 12:03:20 -07:00
2018-07-11 11:38:59 -07:00
print ( " All done; for reference, the current time is " , time . strftime ( " % H: % M: % S " ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
2018-04-03 20:16:31 +02:00
2018-02-28 19:48:39 -08:00
# TODO: Perhaps have option for bullets (pulses) to fade out and in at ends of line, instead of
# jarringly popping out and in?
2018-01-30 18:06:19 -08:00
#
# 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
2018-04-03 20:16:57 +02:00
ContinualAnimation . __init__ ( self , VGroup ( * self . bullets ) , * * kwargs )
2018-01-30 18:06:19 -08:00
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 ,
2018-01-31 17:17:58 -08:00
fdiv ( ( i + alpha ) , ( self . num_bullets ) ) )
2018-01-30 18:06:19 -08:00
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
2018-02-12 13:53:33 -08:00
# 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 ) ]
2018-01-30 18:06:19 -08:00
arrows_vgroup = VGroup ( * arrows )
2019-02-05 15:39:58 -08:00
self . play ( ShowCreation ( arrows_vgroup ) , run_time = 2.5 , rate_func = linear )
2018-01-30 18:06:19 -08:00
self . wait ( )
class FuncRotater ( Animation ) :
CONFIG = {
2018-02-28 19:48:39 -08:00
" rev_func " : lambda x : x , # Func from alpha to CCW revolutions,
2018-01-30 18:06:19 -08:00
}
# Perhaps abstract this out into an "Animation updating from original object" class
2019-02-08 12:00:51 -08:00
def interpolate_submobject ( self , submobject , starting_submobject , alpha ) :
2018-01-30 18:06:19 -08:00
submobject . points = np . array ( starting_submobject . points )
2019-02-08 11:57:27 -08:00
def interpolate_mobject ( self , alpha ) :
Animation . interpolate_mobject ( self , alpha )
2018-02-28 19:48:39 -08:00
angle_revs = self . rev_func ( alpha )
2018-01-30 18:06:19 -08:00
self . mobject . rotate (
2018-02-22 12:05:57 -08:00
angle_revs * TAU ,
2018-01-30 18:06:19 -08:00
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 ,
2018-02-28 19:48:39 -08:00
rev_func = lambda x : x % 0.25 ,
2018-01-30 18:06:19 -08:00
run_time = 10 ) )
# TODO: Be careful about clockwise vs. counterclockwise convention throughout!
# Make sure this is correct everywhere in resulting video.
2018-02-22 12:05:57 -08:00
class OdometerScene ( ColorMappedObjectsScene ) :
2018-01-30 18:06:19 -08:00
CONFIG = {
2018-02-22 12:05:57 -08:00
# "func" : lambda p : 100 * p # Full coloring, essentially
2018-03-20 16:57:06 -07:00
" rotate_func " : lambda x : 2 * np . sin ( 2 * x * TAU ) , # This is given in terms of CW revs
" run_time " : 40 ,
2018-01-30 18:06:19 -08:00
" dashed_line_angle " : None ,
2018-03-20 16:57:06 -07:00
" biased_display_start " : None ,
" pure_odometer_background " : False
2018-01-30 18:06:19 -08:00
}
def construct ( self ) :
2018-02-22 12:05:57 -08:00
ColorMappedObjectsScene . construct ( self )
2018-03-20 16:57:06 -07:00
radius = ODOMETER_RADIUS
2018-01-30 18:06:19 -08:00
circle = Circle ( center = ORIGIN , radius = radius )
2018-03-20 16:57:06 -07:00
circle . stroke_width = ODOMETER_STROKE_WIDTH
2018-02-22 12:05:57 -08:00
circle . color_using_background_image ( self . background_image_file )
2018-01-30 18:06:19 -08:00
self . add ( circle )
2018-03-20 16:57:06 -07:00
if self . pure_odometer_background :
# Just display this background circle, for compositing in Premiere with PiWalker odometers
self . wait ( )
return
2018-01-30 18:06:19 -08:00
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 )
2018-02-22 12:05:57 -08:00
num_display = DecimalNumber ( 0 , include_background_rectangle = False )
2018-01-30 18:06:19 -08:00
num_display . move_to ( 2 * DOWN )
2018-02-22 12:05:57 -08:00
caption = TextMobject ( " turns clockwise " )
caption . next_to ( num_display , DOWN )
self . add ( caption )
2018-01-30 18:06:19 -08:00
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 (
2018-02-28 19:48:39 -08:00
FuncRotater ( base_arrow , rev_func = lambda x : - self . rotate_func ( x ) ) ,
2018-01-30 18:06:19 -08:00
ChangingDecimal ( num_display , display_func ) ,
run_time = self . run_time ,
2019-02-05 15:39:58 -08:00
rate_func = linear )
2018-01-30 18:06:19 -08:00
2018-01-24 13:11:44 -08:00
#############
# 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 ,
2018-02-12 13:53:33 -08:00
" graph_origin " : 2.5 * DOWN + 5.5 * LEFT ,
2018-01-24 13:11:44 -08:00
" 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 ,
2018-03-05 17:58:57 -08:00
" num_iterations " : 5 ,
2018-01-24 13:11:44 -08:00
" iteration_at_which_to_start_zoom " : 3 ,
" graph_label " : " y = x^2 " ,
" show_target_line " : True ,
2018-03-09 12:07:54 -08:00
" x_tick_frequency " : 0.25
2018-01-24 13:11:44 -08:00
}
2018-03-05 17:58:57 -08:00
class TestFirstSqrtScene ( FirstSqrtScene ) :
CONFIG = {
" num_iterations " : 1 ,
}
2018-02-12 13:53:33 -08:00
FirstSqrtSceneConfig = FirstSqrtScene . CONFIG
shiftVal = FirstSqrtSceneConfig [ " targetY " ]
class SecondSqrtScene ( FirstSqrtScene ) :
CONFIG = {
" graph_label " : FirstSqrtSceneConfig [ " graph_label " ] + " - " + str ( shiftVal ) ,
2018-03-05 17:58:57 -08:00
" show_y_as_deviation " : True ,
2018-02-12 13:53:33 -08:00
}
2018-01-29 13:39:56 -08:00
2018-03-05 17:58:57 -08:00
class TestSecondSqrtScene ( SecondSqrtScene ) :
CONFIG = {
" num_iterations " : 1
}
class GuaranteedZeroScene ( SecondSqrtScene ) :
CONFIG = {
# Manual config values, not automatically synced to anything above
" initial_lower_x " : 1.75 ,
" initial_upper_x " : 2
}
class TestGuaranteedZeroScene ( GuaranteedZeroScene ) :
CONFIG = {
" num_iterations " : 1
}
2018-02-12 13:53:33 -08:00
# TODO: Pi creatures intrigued
2018-01-29 13:39:56 -08:00
2018-02-15 11:57:45 -08:00
class RewriteEquation ( Scene ) :
def construct ( self ) :
2018-02-21 17:12:45 -08:00
# Can maybe use get_center() to perfectly center Groups before and after transform
2018-02-15 11:57:45 -08:00
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 )
2018-02-21 17:12:45 -08:00
# where_old = TextMobject("Where does ")
# where_old.next_to(f_old, LEFT)
# where_new = where_old.copy()
# where_new.next_to(f_new, LEFT)
2018-02-15 11:57:45 -08:00
2018-02-21 17:12:45 -08:00
# qmark_old = TextMobject("?")
# qmark_old.next_to(g_old, RIGHT)
# qmark_new = qmark_old.copy()
# qmark_new.next_to(zero_new, RIGHT)
self . add ( f_old , equals_old , equals_old_2 , g_old ) #, where_old, qmark_old)
2018-02-15 11:57:45 -08:00
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 ) ,
2018-02-21 17:12:45 -08:00
# ReplacementTransform(where_old, where_new),
# ReplacementTransform(qmark_old, qmark_new),
2018-02-15 11:57:45 -08:00
)
self . wait ( )
2018-02-12 13:53:33 -08:00
class SignsExplanation ( Scene ) :
2018-01-29 13:39:56 -08:00
def construct ( self ) :
2018-03-20 16:57:06 -07:00
num_line = NumberLine ( )
2018-02-12 13:53:33 -08:00
largest_num = 10
2018-08-09 17:56:05 -07:00
num_line . add_numbers ( * list ( range ( - largest_num , largest_num + 1 ) ) )
2018-02-12 13:53:33 -08:00
self . add ( num_line )
self . wait ( )
2018-01-29 13:39:56 -08:00
2018-02-12 13:53:33 -08:00
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 ,
2018-02-15 11:57:45 -08:00
color = positive_color )
2018-02-12 13:53:33 -08:00
neg_arrow = Arrow (
num_line . number_to_point ( 0 ) ,
num_line . number_to_point ( neg_num ) ,
buff = 0 ,
2018-02-15 11:57:45 -08:00
color = negative_color )
2018-02-20 21:54:40 -08:00
plus_sign = TexMobject ( " + " , fill_color = positive_color )
minus_sign = TexMobject ( " - " , fill_color = negative_color )
plus_sign . next_to ( pos_arrow , UP )
minus_sign . next_to ( neg_arrow , UP )
2018-02-12 13:53:33 -08:00
#num_line.add_numbers(pos_num)
2018-02-20 21:54:40 -08:00
self . play ( ShowCreation ( pos_arrow ) , FadeIn ( plus_sign ) )
2018-01-29 13:39:56 -08:00
2018-02-12 13:53:33 -08:00
#num_line.add_numbers(neg_num)
2018-02-20 21:54:40 -08:00
self . play ( ShowCreation ( neg_arrow ) , FadeIn ( minus_sign ) )
2018-02-12 13:53:33 -08:00
class VectorField ( Scene ) :
CONFIG = {
2018-02-28 19:48:39 -08:00
" func " : example_plane_func ,
2018-02-12 13:53:33 -08:00
" granularity " : 10 ,
" arrow_scale_factor " : 0.1 ,
" normalized_arrow_scale_factor " : 5
}
def construct ( self ) :
num_plane = NumberPlane ( )
self . add ( num_plane )
2018-03-30 11:25:37 -07:00
x_min , y_min = num_plane . point_to_coords ( FRAME_X_RADIUS * LEFT + FRAME_Y_RADIUS * UP )
x_max , y_max = num_plane . point_to_coords ( FRAME_X_RADIUS * RIGHT + FRAME_Y_RADIUS * DOWN )
2018-02-12 13:53:33 -08:00
2018-02-15 15:37:19 -08:00
x_points = np . linspace ( x_min , x_max , self . granularity )
y_points = np . linspace ( y_min , y_max , self . granularity )
2018-02-12 13:53:33 -08:00
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 ) :
2018-03-05 17:58:57 -08:00
CONFIG = {
" camera_config " : { " use_z_coordinate_for_display_order " : True } ,
}
2018-02-21 17:12:45 -08:00
2018-02-12 13:53:33 -08:00
def construct ( self ) :
num_line = NumberLine ( )
num_line . add_numbers ( )
self . add ( num_line )
self . wait ( )
2018-02-28 19:48:39 -08:00
# We arrange to go from 2 to 4, a la the squaring in FirstSqrtScene
base_point = num_line . number_to_point ( 2 ) + OUT
2018-02-21 17:12:45 -08:00
dot_color = ORANGE
2018-03-05 17:58:57 -08:00
DOT_Z = OUT
# Note: This z-buffer value is needed for our static scenes, but is
# not sufficient for everything, in that we still need to use
# the foreground_mobjects trick during animations.
# At some point, we should figure out how to have animations
# play well with z coordinates.
2018-02-21 17:12:45 -08:00
2018-03-05 17:58:57 -08:00
input_dot = Dot ( base_point + DOT_Z , color = dot_color )
2018-03-09 10:32:19 -08:00
input_label = TextMobject ( " Input " , fill_color = dot_color )
2018-02-21 17:12:45 -08:00
input_label . next_to ( input_dot , UP + LEFT )
input_label . add_background_rectangle ( )
2018-03-05 17:58:57 -08:00
self . add_foreground_mobject ( input_dot )
self . add ( input_label )
2018-02-21 17:12:45 -08:00
curved_arrow = Arc ( 0 , color = MAROON_E )
curved_arrow . set_bound_angles ( np . pi , 0 )
curved_arrow . generate_points ( )
curved_arrow . add_tip ( )
curved_arrow . move_arc_center_to ( base_point + RIGHT )
2018-02-22 12:05:57 -08:00
# Could do something smoother, with arrowhead moving along partial arc?
2018-02-21 17:12:45 -08:00
self . play ( ShowCreation ( curved_arrow ) )
2018-03-05 17:58:57 -08:00
output_dot = Dot ( base_point + 2 * RIGHT + DOT_Z , color = dot_color )
2018-03-09 10:32:19 -08:00
output_label = TextMobject ( " Output " , fill_color = dot_color )
2018-02-21 17:12:45 -08:00
output_label . next_to ( output_dot , UP + RIGHT )
output_label . add_background_rectangle ( )
2018-03-05 17:58:57 -08:00
self . add_foreground_mobject ( output_dot )
self . add ( output_label )
2018-02-21 17:12:45 -08:00
self . wait ( )
2018-02-12 13:53:33 -08:00
num_plane = NumberPlane ( )
num_plane . add_coordinates ( )
2018-02-21 17:12:45 -08:00
new_base_point = base_point + 2 * UP
new_input_dot = input_dot . copy ( ) . move_to ( new_base_point )
new_input_label = input_label . copy ( ) . next_to ( new_input_dot , UP + LEFT )
new_curved_arrow = Arc ( 0 ) . match_style ( curved_arrow )
new_curved_arrow . set_bound_angles ( np . pi * 3 / 4 , 0 )
new_curved_arrow . generate_points ( )
new_curved_arrow . add_tip ( )
input_diff = input_dot . get_center ( ) - curved_arrow . points [ 0 ]
output_diff = output_dot . get_center ( ) - curved_arrow . points [ - 1 ]
new_curved_arrow . shift ( ( new_input_dot . get_center ( ) - new_curved_arrow . points [ 0 ] ) - input_diff )
new_output_dot = output_dot . copy ( ) . move_to ( new_curved_arrow . points [ - 1 ] + output_diff )
new_output_label = output_label . copy ( ) . next_to ( new_output_dot , UP + RIGHT )
dot_objects = Group ( input_dot , input_label , output_dot , output_label , curved_arrow )
new_dot_objects = Group ( new_input_dot , new_input_label , new_output_dot , new_output_label , new_curved_arrow )
self . play (
FadeOut ( num_line ) , FadeIn ( num_plane ) ,
ReplacementTransform ( dot_objects , new_dot_objects ) ,
)
2018-02-12 13:53:33 -08:00
self . wait ( )
2018-02-21 17:12:45 -08:00
self . add_foreground_mobject ( new_dot_objects )
2018-02-12 13:53:33 -08:00
complex_plane = ComplexPlane ( )
complex_plane . add_coordinates ( )
2018-02-21 17:12:45 -08:00
# This looks a little wonky and we may wish to do a crossfade in Premiere instead
2018-02-12 13:53:33 -08:00
self . play ( FadeOut ( num_plane ) , FadeIn ( complex_plane ) )
2018-02-21 17:12:45 -08:00
self . wait ( )
2018-02-12 13:53:33 -08:00
2018-01-24 13:11:44 -08:00
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 ( )
2018-01-26 12:26:30 -08:00
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 )
2018-01-29 13:39:56 -08:00
# TODO: Make this line a thin rectangle
2018-01-26 12:26:30 -08:00
interval_1d = Line ( left_point , right_point ,
stroke_color = inner_color , stroke_width = stroke_width )
2018-01-29 13:39:56 -08:00
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 )
2018-01-26 12:26:30 -08:00
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 )
2018-01-29 13:39:56 -08:00
full_1d = VGroup ( rect_1d , endpoints_1d )
2018-01-26 12:26:30 -08:00
self . play ( ShowCreation ( full_1d ) )
2018-01-24 13:11:44 -08:00
self . wait ( )
2018-01-29 13:39:56 -08:00
# TODO: Can polish the morphing above; have dots become left and right sides, and
# only then fill in the top and bottom
2018-01-24 13:11:44 -08:00
num_plane = NumberPlane ( )
2018-01-26 12:26:30 -08:00
random_points = [ UP + LEFT , UP + RIGHT , DOWN + RIGHT , DOWN + LEFT ]
border_2d = Polygon (
* random_points ,
stroke_color = border_color ,
stroke_width = stroke_width )
2018-01-24 13:11:44 -08:00
2018-01-26 12:26:30 -08:00
filling_2d = Polygon (
2018-01-24 13:11:44 -08:00
* random_points ,
2018-01-26 12:26:30 -08:00
fill_color = inner_color ,
fill_opacity = 0.8 ,
stroke_width = stroke_width )
full_2d = VGroup ( filling_2d , border_2d )
2018-01-24 13:11:44 -08:00
self . play (
FadeOut ( num_line ) ,
FadeIn ( num_plane ) ,
2018-01-26 12:26:30 -08:00
ReplacementTransform ( full_1d , full_2d ) )
2018-01-24 13:11:44 -08:00
self . wait ( )
2018-02-26 16:07:50 -08:00
class Initial2dFuncSceneBase ( Scene ) :
CONFIG = {
2018-03-05 17:58:57 -08:00
" func " : point3d_func_from_complex_func ( lambda c : c * * 2 - c * * 3 / 5 + 1 )
# We don't use example_plane_func because, unfortunately, the sort of examples
# which are good for demonstrating our color mapping haven't turned out to be
# good for visualizing in this manner; the gridlines run over themselves multiple
# times in too confusing a fashion
2018-02-26 16:07:50 -08:00
}
def show_planes ( self ) :
2018-07-11 11:38:59 -07:00
print ( " Error! Unimplemented (pure virtual) show_planes " )
2018-02-26 16:07:50 -08:00
def shared_construct ( self ) :
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 )
2018-03-05 17:58:57 -08:00
def wiggle_around ( point ) :
radius = 0.2
small_circle = cw_circle . copy ( )
small_circle . scale ( radius )
small_circle . move_to ( point + radius * RIGHT )
small_circle . set_color ( RED )
self . obj_draw ( small_circle )
2018-02-26 16:07:50 -08:00
2018-03-05 17:58:57 -08:00
wiggle_around ( points [ - 1 ] )
2018-02-26 16:07:50 -08:00
def obj_draw ( self , input_object ) :
self . play ( ShowCreation ( input_object ) )
def construct ( self ) :
self . show_planes ( )
self . shared_construct ( )
# Alternative to the below, using MappingCameras, but no morphing animation
class Initial2dFuncSceneWithoutMorphing ( Initial2dFuncSceneBase ) :
def setup ( self ) :
left_camera = Camera ( * * self . camera_config )
right_camera = MappingCamera (
mapping_func = self . func ,
* * self . camera_config )
split_screen_camera = SplitScreenCamera ( left_camera , right_camera , * * self . camera_config )
self . camera = split_screen_camera
def show_planes ( self ) :
self . num_plane = NumberPlane ( )
self . num_plane . prepare_for_nonlinear_transform ( )
#num_plane.fade()
self . add ( self . num_plane )
2018-01-29 13:39:56 -08:00
2018-02-26 16:07:50 -08:00
# Alternative to the above, manually implementing split screen with a morphing animation
class Initial2dFuncSceneMorphing ( Initial2dFuncSceneBase ) :
2018-01-29 13:39:56 -08:00
CONFIG = {
2019-02-05 11:02:15 -08:00
" num_needed_anchor_curves " : 10 ,
2018-01-29 13:39:56 -08:00
}
def setup ( self ) :
2018-03-30 11:25:37 -07:00
split_line = DashedLine ( FRAME_Y_RADIUS * UP , FRAME_Y_RADIUS * DOWN )
self . num_plane = NumberPlane ( x_radius = FRAME_X_RADIUS / 2 )
2018-01-29 13:39:56 -08:00
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 ) :
2018-03-30 11:25:37 -07:00
object . shift ( FRAME_X_RADIUS / 2 * LEFT )
2018-01-29 13:39:56 -08:00
def squash_onto_right ( self , object ) :
2018-03-30 11:25:37 -07:00
object . shift ( FRAME_X_RADIUS / 2 * RIGHT )
2018-01-29 13:39:56 -08:00
def obj_draw ( self , input_object ) :
output_object = input_object . copy ( )
2019-02-05 11:02:15 -08:00
if input_object . get_num_curves ( ) < self . num_needed_anchor_curves :
input_object . insert_n_curves ( self . num_needed_anchor_curves )
2018-01-29 13:39:56 -08:00
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 )
)
2018-02-26 16:07:50 -08:00
def show_planes ( self ) :
2018-01-29 13:39:56 -08:00
right_plane = self . num_plane . copy ( )
right_plane . center ( )
right_plane . prepare_for_nonlinear_transform ( )
right_plane . apply_function ( self . func )
2018-03-30 11:25:37 -07:00
right_plane . shift ( FRAME_X_RADIUS / 2 * RIGHT )
2018-01-29 13:39:56 -08:00
self . right_plane = right_plane
crappy_cropper = FullScreenFadeRectangle ( fill_opacity = 1 )
2018-03-30 11:25:37 -07:00
crappy_cropper . stretch_to_fit_width ( FRAME_X_RADIUS )
2018-01-29 13:39:56 -08:00
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
)
2018-02-22 12:05:57 -08:00
class DemonstrateColorMapping ( ColorMappedObjectsScene ) :
CONFIG = {
2018-03-20 16:57:06 -07:00
" show_num_plane " : False ,
" show_full_color_map " : True
2018-02-22 12:05:57 -08:00
}
def construct ( self ) :
ColorMappedObjectsScene . construct ( self )
2018-03-20 16:57:06 -07:00
# Doing this in Premiere now instead
# output_plane_label = TextMobject("Output Plane", color = WHITE)
# output_plane_label.move_to(3 * UP)
# self.add_foreground_mobject(output_plane_label)
if self . show_full_color_map :
2018-03-30 11:25:37 -07:00
bright_background = Rectangle ( width = 2 * FRAME_X_RADIUS + 1 , height = 2 * FRAME_Y_RADIUS + 1 , fill_opacity = 1 )
2018-03-20 16:57:06 -07:00
bright_background . color_using_background_image ( self . background_image_file )
dim_background = bright_background . copy ( )
dim_background . fill_opacity = 0.3
background = bright_background . copy ( )
self . add ( background )
self . wait ( )
self . play ( ReplacementTransform ( background , dim_background ) )
self . wait ( )
2018-02-22 12:05:57 -08:00
2018-02-28 19:48:39 -08:00
ray = Line ( ORIGIN , 10 * LEFT )
2018-03-20 16:57:06 -07:00
circle = cw_circle . copy ( )
circle . color_using_background_image ( self . background_image_file )
2018-02-22 12:05:57 -08:00
self . play ( ShowCreation ( circle ) )
2018-03-20 16:57:06 -07:00
self . wait ( )
2018-02-22 12:05:57 -08:00
2018-02-28 19:48:39 -08:00
scale_up_factor = 5
scale_down_factor = 20
2018-03-20 16:57:06 -07:00
self . play ( ApplyMethod ( circle . scale , fdiv ( 1 , scale_down_factor ) ) )
self . play ( ApplyMethod ( circle . scale , scale_up_factor * scale_down_factor ) )
self . play ( ApplyMethod ( circle . scale , fdiv ( 1 , scale_up_factor ) ) )
self . wait ( )
self . remove ( circle )
ray = Line ( ORIGIN , 10 * LEFT )
ray . color_using_background_image ( self . background_image_file )
2018-02-22 12:05:57 -08:00
2018-03-20 16:57:06 -07:00
self . play ( ShowCreation ( ray ) )
2018-02-22 12:05:57 -08:00
2018-03-20 16:57:06 -07:00
self . wait ( )
2018-02-22 12:05:57 -08:00
2018-03-20 16:57:06 -07:00
self . play ( Rotating ( ray , about_point = ORIGIN , radians = - TAU / 2 ) )
2018-02-22 12:05:57 -08:00
2018-03-20 16:57:06 -07:00
self . wait ( )
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
self . play ( Rotating ( ray , about_point = ORIGIN , radians = - TAU / 2 ) )
self . wait ( )
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
if self . show_full_color_map :
self . play ( ReplacementTransform ( background , bright_background ) )
self . wait ( )
2018-01-24 13:11:44 -08:00
2018-03-22 15:02:20 -07:00
# Everything in this is manually kept in sync with WindingNumber_G/TransitionFromPathsToBoundaries
2018-03-05 17:58:57 -08:00
class LoopSplitScene ( ColorMappedObjectsScene ) :
2018-01-30 13:55:59 -08:00
CONFIG = {
2018-03-22 15:02:20 -07:00
" func " : plane_func_by_wind_spec (
( - 2 , 0 , 2 ) , ( 2 , 0 , 1 )
) ,
2018-03-05 17:58:57 -08:00
" use_fancy_lines " : True ,
2018-01-30 13:55:59 -08:00
}
2018-01-24 13:11:44 -08:00
2018-01-30 13:55:59 -08:00
def PulsedLine ( self ,
start , end ,
bullet_template ,
num_bullets = 4 ,
pulse_time = 1 ,
* * kwargs ) :
2018-03-22 15:02:20 -07:00
line = Line ( start , end , color = WHITE , stroke_width = 4 , * * kwargs )
2018-03-05 17:58:57 -08:00
if self . use_fancy_lines :
line . color_using_background_image ( self . background_image_file )
2018-01-30 13:55:59 -08:00
anim = LinePulser (
line = line ,
bullet_template = bullet_template ,
num_bullets = num_bullets ,
pulse_time = pulse_time ,
2018-03-05 17:58:57 -08:00
output_func = self . func ,
2018-01-30 13:55:59 -08:00
* * kwargs )
2018-03-20 16:57:06 -07:00
return ( line , VMobject ( * anim . bullets ) , anim )
2018-01-24 13:11:44 -08:00
def construct ( self ) :
2018-03-05 17:58:57 -08:00
ColorMappedObjectsScene . construct ( self )
2018-01-24 13:11:44 -08:00
scale_factor = 2
shift_term = 0
2018-02-28 19:48:39 -08:00
# TODO: Change all this to use a wider than tall loop, made of two squares
2018-01-24 13:11:44 -08:00
# Original loop
2018-03-20 16:57:06 -07:00
tl = ( UP + 2 * LEFT ) * scale_factor
tm = UP * scale_factor
tr = ( UP + 2 * RIGHT ) * scale_factor
bl = ( DOWN + 2 * LEFT ) * scale_factor
bm = DOWN * scale_factor
br = ( DOWN + 2 * RIGHT ) * scale_factor
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
top_line = Line ( tl , tr ) # Invisible; only used for surrounding circle
bottom_line = Line ( br , bl ) # Invisible; only used for surrounding circle
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
stroke_width = top_line . stroke_width
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
default_bullet = PiCreature ( )
default_bullet . scale ( 0.15 )
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
def pl ( a , b ) :
return self . PulsedLine ( a , b , default_bullet )
2018-01-24 13:11:44 -08:00
2018-03-22 11:54:08 -07:00
def indicate_circle ( x , double_horizontal_stretch = False ) :
circle = Circle ( color = WHITE , radius = 2 * np . sqrt ( 2 ) )
circle . move_to ( x . get_center ( ) )
if x . get_slope ( ) == 0 :
circle . stretch ( 0.2 , 1 )
if double_horizontal_stretch :
circle . stretch ( 2 , 0 )
else :
circle . stretch ( 0.2 , 0 )
2018-03-20 16:57:06 -07:00
return circle
2018-01-24 13:11:44 -08:00
2018-03-20 16:57:06 -07:00
tl_line_trip = pl ( tl , tm )
2018-03-22 11:54:08 -07:00
midline_left_trip = pl ( tm , bm )
2018-03-20 16:57:06 -07:00
bl_line_trip = pl ( bm , bl )
left_line_trip = pl ( bl , tl )
2018-03-22 11:54:08 -07:00
left_square_trips = [ tl_line_trip , midline_left_trip , bl_line_trip , left_line_trip ]
2018-03-20 16:57:06 -07:00
left_square_lines = [ x [ 0 ] for x in left_square_trips ]
left_square_lines_vmobject = VMobject ( * left_square_lines )
left_square_bullets = [ x [ 1 ] for x in left_square_trips ]
left_square_anims = [ x [ 2 ] for x in left_square_trips ]
tr_line_trip = pl ( tm , tr )
right_line_trip = pl ( tr , br )
br_line_trip = pl ( br , bm )
2018-03-22 11:54:08 -07:00
midline_right_trip = pl ( bm , tm )
2018-03-20 16:57:06 -07:00
2018-03-22 11:54:08 -07:00
right_square_trips = [ tr_line_trip , right_line_trip , br_line_trip , midline_right_trip ]
2018-03-20 16:57:06 -07:00
right_square_lines = [ x [ 0 ] for x in right_square_trips ]
right_square_lines_vmobject = VMobject ( * right_square_lines )
right_square_bullets = [ x [ 1 ] for x in right_square_trips ]
right_square_anims = [ x [ 2 ] for x in right_square_trips ]
2018-03-22 11:54:08 -07:00
midline_trips = [ midline_left_trip , midline_right_trip ]
midline_lines = [ x [ 0 ] for x in midline_trips ]
midline_lines_vmobject = VMobject ( * midline_lines )
midline_bullets = [ x [ 1 ] for x in midline_trips ]
midline_anims = [ x [ 1 ] for x in midline_trips ]
left_line = left_line_trip [ 0 ]
right_line = right_line_trip [ 0 ]
2018-03-20 16:57:06 -07:00
for b in left_square_bullets + right_square_bullets :
b . set_fill ( opacity = 0 )
2018-03-22 15:02:20 -07:00
faded = 0.3
# Workaround for FadeOut/FadeIn not playing well with ContinualAnimations due to
# Transforms making copies no longer identified with the ContinualAnimation's tracked mobject
def bullet_fade ( start , end , mob ) :
return UpdateFromAlphaFunc ( mob , lambda m , a : m . set_fill ( opacity = interpolate ( start , end , a ) ) )
def bullet_list_fade ( start , end , bullet_list ) :
2018-08-09 17:56:05 -07:00
return [ bullet_fade ( start , end , b ) for b in bullet_list ]
2018-03-22 15:02:20 -07:00
def line_fade ( start , end , mob ) :
return UpdateFromAlphaFunc ( mob , lambda m , a : m . set_stroke ( width = interpolate ( start , end , a ) * stroke_width ) )
2018-03-22 11:54:08 -07:00
2018-03-22 15:02:20 -07:00
def play_combined_fade ( start , end , lines_vmobject , bullets ) :
self . play (
line_fade ( start , end , lines_vmobject ) ,
* bullet_list_fade ( start , end , bullets )
)
def play_fade_left ( start , end ) :
play_combined_fade ( start , end , left_square_lines_vmobject , left_square_bullets )
2018-03-22 11:54:08 -07:00
2018-03-22 15:02:20 -07:00
def play_fade_right ( start , end ) :
play_combined_fade ( start , end , right_square_lines_vmobject , right_square_bullets )
def play_fade_mid ( start , end ) :
play_combined_fade ( start , end , midline_lines_vmobject , midline_bullets )
2018-03-22 11:54:08 -07:00
def flash_circles ( circles ) :
2019-02-14 12:06:21 -08:00
self . play ( LaggedStartMap ( FadeIn , VGroup ( circles ) ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
self . play ( FadeOut ( VGroup ( circles ) ) )
self . wait ( )
2018-03-22 11:54:08 -07:00
2018-03-22 15:02:20 -07:00
self . add ( left_square_lines_vmobject , right_square_lines_vmobject )
self . remove ( * midline_lines )
self . wait ( )
self . play ( ShowCreation ( midline_lines [ 0 ] ) )
self . add ( midline_lines_vmobject )
self . wait ( )
2018-03-20 16:57:06 -07:00
2018-03-22 15:02:20 -07:00
self . add ( * left_square_anims )
self . play ( line_fade ( 1 , faded , right_square_lines_vmobject ) , * bullet_list_fade ( 0 , 1 , left_square_bullets ) )
self . wait ( )
2018-03-22 11:54:08 -07:00
flash_circles ( [ indicate_circle ( l ) for l in left_square_lines ] )
2018-03-22 15:02:20 -07:00
self . play ( line_fade ( faded , 1 , right_square_lines_vmobject ) , * bullet_list_fade ( 1 , 0 , left_square_bullets ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
2018-03-22 11:54:08 -07:00
2018-03-22 15:02:20 -07:00
self . add ( * right_square_anims )
self . play ( line_fade ( 1 , faded , left_square_lines_vmobject ) , * bullet_list_fade ( 0 , 1 , right_square_bullets ) )
self . wait ( )
2018-03-22 11:54:08 -07:00
flash_circles ( [ indicate_circle ( l ) for l in right_square_lines ] )
2018-03-22 15:02:20 -07:00
self . play ( line_fade ( faded , 1 , left_square_lines_vmobject ) , * bullet_list_fade ( 1 , 0 , right_square_bullets ) )
2018-03-23 04:16:03 -07:00
self . wait ( )
2018-04-03 20:16:57 +02:00
2018-04-03 20:16:31 +02:00
self . play ( * bullet_list_fade ( 0 , 1 , left_square_bullets + right_square_bullets ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
2018-03-22 11:54:08 -07:00
outside_circlers = [
indicate_circle ( left_line ) ,
indicate_circle ( right_line ) ,
indicate_circle ( top_line , double_horizontal_stretch = True ) ,
indicate_circle ( bottom_line , double_horizontal_stretch = True )
]
flash_circles ( outside_circlers )
2018-01-24 13:11:44 -08:00
2018-03-22 11:54:08 -07:00
inner_circle = indicate_circle ( midline_lines [ 0 ] )
self . play ( FadeIn ( inner_circle ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
2018-03-23 04:16:03 -07:00
self . play ( FadeOut ( inner_circle ) , line_fade ( 1 , 0 , midline_lines_vmobject ) , * bullet_list_fade ( 1 , 0 , midline_bullets ) )
2018-04-03 20:16:57 +02:00
self . wait ( )
# Repeat for effect, goes well with narration
self . play ( FadeIn ( inner_circle ) , line_fade ( 0 , 1 , midline_lines_vmobject ) , * bullet_list_fade ( 0 , 1 , midline_bullets ) )
self . wait ( )
self . play ( FadeOut ( inner_circle ) , line_fade ( 1 , 0 , midline_lines_vmobject ) , * bullet_list_fade ( 1 , 0 , midline_bullets ) )
self . wait ( )
2018-01-24 13:11:44 -08:00
# 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 = {
2018-02-28 19:48:39 -08:00
" func " : plane_func_by_wind_spec ( ( 1 , 2 ) , ( - 1 , 1.5 ) , ( - 1 , 1.5 ) ) ,
2018-02-21 17:12:45 -08:00
" num_iterations " : 2 ,
2018-01-24 13:11:44 -08:00
}
2018-03-20 16:57:06 -07:00
class SolveX5MinusXMinus1 ( EquationSolver2d ) :
2018-04-03 20:16:57 +02:00
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : c * * 5 - c - 1 ) ,
" num_iterations " : 10 ,
" show_cursor " : True ,
" display_in_bfs " : True ,
}
class PureColorMapOfX5Thing ( PureColorMap ) :
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : c * * 5 - c - 1 ) ,
}
class X5ThingWithRightHalfGreyed ( SolveX5MinusXMinus1 ) :
CONFIG = {
" num_iterations " : 3 ,
" manual_wind_override " : ( 1 , None , ( 1 , ( 0 , None , None ) , ( 0 , None , None ) ) )
}
class SolveX5MinusXMinus1_5Iterations ( EquationSolver2d ) :
2018-03-20 16:57:06 -07:00
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : c * * 5 - c - 1 ) ,
" num_iterations " : 5 ,
2018-04-03 20:16:57 +02:00
" show_cursor " : True ,
" display_in_bfs " : True ,
" manual_wind_override " : ( None , None , ( None , ( 0 , None , None ) , ( 0 , None , None ) ) )
2018-03-20 16:57:06 -07:00
}
2018-04-03 20:16:57 +02:00
class X5_Monster_Red_Lines ( SolveX5MinusXMinus1_5Iterations ) :
CONFIG = {
" use_separate_plays " : True ,
" use_fancy_lines " : False ,
" line_color " : RED ,
}
class X5_Monster_Green_Lines ( X5_Monster_Red_Lines ) :
CONFIG = {
" line_color " : GREEN ,
}
class X5_Monster_Red_Lines_Long ( X5_Monster_Red_Lines ) :
CONFIG = {
" num_iterations " : 6
}
class X5_Monster_Green_Lines_Long ( X5_Monster_Green_Lines ) :
CONFIG = {
" num_iterations " : 6
}
class X5_Monster_Red_Lines_Little_More ( X5_Monster_Red_Lines_Long ) :
CONFIG = {
" num_iterations " : 7
}
class X5_Monster_Green_Lines_Little_More ( X5_Monster_Green_Lines_Long ) :
CONFIG = {
" num_iterations " : 7
}
class X5_Monster_Red_Lines_No_Numbers ( X5_Monster_Red_Lines ) :
CONFIG = {
" num_iterations " : 3 ,
" show_winding_numbers " : False ,
}
class X5_Monster_Green_Lines_No_Numbers ( X5_Monster_Green_Lines ) :
CONFIG = {
" num_iterations " : 3 ,
" show_winding_numbers " : False ,
}
class SolveX5MinusXMinus1_3Iterations ( EquationSolver2d ) :
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : c * * 5 - c - 1 ) ,
" num_iterations " : 3 ,
" show_cursor " : True ,
" display_in_bfs " : True ,
}
class Diagnostic ( SolveX5MinusXMinus1_3Iterations ) :
CONFIG = {
# I think the combination of these two makes things slow
" use_separate_plays " : not False , # This one isn't important to set any particular way, so let's leave it like this
" use_fancy_lines " : True ,
# This causes a small slowdown (before rendering, in particular), but not the big one, I think
" show_winding_numbers " : True ,
# This doesn't significantly matter for rendering time, I think
" camera_config " : { " use_z_coordinate_for_display_order " : True }
}
# All above flags False (meaning not db = False): just under 30 it/s
# not db = True: 30
# use_fancy_lines = True: 30 at first (if scene.play(bfs_nodes[0].first_anim, border_anim is off), but then drops to 3 (or drops right away if that simultaneous play is on)
# use_z_coordinate = True: 30
# show_winding_numbers = True: 10
# winding AND use_fancy_lines: 10
# not db AND fancy_lines AND z_coords = true, winding = false: 3. Not 30, but 3. Slow.
# db AND use_fancy: 3. Slow.
# fancy AND z_coords: 30. Fast. [Hm, this may have been a mistake; fancy and z_coords is now slow?]
# fancy, winding, AND z_coords, but not (not db): 10
# not db, winding, AND z_coords, but not fancy: 10
# class DiagnosticB(Diagnostic):
# CONFIG = {
# "num_iterations" : 3,
# #"num_checkpoints" : 100,
# #"show_winding_numbers" : False,
# #"use_cheap_winding_numbers" : True,
# }
2018-03-22 11:54:08 -07:00
class SolveX5MinusXMinus1Parallel ( SolveX5MinusXMinus1 ) :
2018-03-20 16:57:06 -07:00
CONFIG = {
" display_in_parallel " : True
}
2018-03-22 11:54:08 -07:00
class SolveX5MinusXMinus1BFS ( SolveX5MinusXMinus1 ) :
CONFIG = {
" display_in_bfs " : True
}
2018-02-28 19:48:39 -08:00
class PreviewClip ( EquationSolver2d ) :
CONFIG = {
" func " : example_plane_func ,
" num_iterations " : 5 ,
" display_in_parallel " : True ,
" use_fancy_lines " : True ,
}
2018-04-03 20:16:57 +02:00
class ParallelClip ( EquationSolver2d ) :
CONFIG = {
" func " : plane_func_by_wind_spec (
( - 3 , - 1.3 , 2 ) , ( 0.1 , 0.2 , 1 ) , ( 2.8 , - 2 , 1 )
) ,
" num_iterations " : 5 ,
" display_in_parallel " : True ,
}
class EquationSolver2dMatchBreakdown ( EquationSolver2d ) :
CONFIG = {
" func " : plane_func_by_wind_spec (
( - 2 , 0.3 , 2 ) , ( 2 , - 0.2 , 1 ) # Not an exact match, because our breakdown function has a zero along midlines...
) ,
" num_iterations " : 5 ,
" display_in_parallel " : True ,
" show_cursor " : True
}
class EquationSolver2dMatchBreakdown_parallel ( EquationSolver2dMatchBreakdown ) :
CONFIG = {
" display_in_parallel " : True ,
" display_in_bfs " : False ,
}
class EquationSolver2dMatchBreakdown_bfs ( EquationSolver2dMatchBreakdown ) :
CONFIG = {
" display_in_parallel " : False ,
" display_in_bfs " : True ,
}
2018-03-20 17:19:29 -07:00
class QuickPreview ( PreviewClip ) :
CONFIG = {
2018-03-22 11:54:08 -07:00
" num_iterations " : 3 ,
2018-03-20 17:19:29 -07:00
" display_in_parallel " : False ,
2018-03-22 11:54:08 -07:00
" display_in_bfs " : True ,
" show_cursor " : True
2018-03-20 17:19:29 -07:00
}
2018-03-22 15:02:20 -07:00
class LongEquationSolver ( EquationSolver2d ) :
CONFIG = {
" func " : example_plane_func ,
" num_iterations " : 10 ,
" display_in_bfs " : True ,
" linger_parameter " : 0.4 ,
" show_cursor " : True ,
}
2018-04-03 20:16:57 +02:00
class QuickPreviewUnfancy ( LongEquationSolver ) :
CONFIG = {
# "use_fancy_lines" : False,
}
2018-01-24 13:11:44 -08:00
# TODO: Borsuk-Ulam visuals
2018-01-29 13:39:56 -08:00
# Note: May want to do an ordinary square scene, then MappingCamera it into a circle
2018-01-26 12:26:30 -08:00
# class BorsukUlamScene(PiWalker):
2018-01-24 13:11:44 -08:00
2018-01-26 12:26:30 -08:00
# 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
}
2018-01-24 13:11:44 -08:00
2018-02-15 11:57:45 -08:00
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 ( )
2018-02-15 14:44:32 -08:00
self . play (
CircleIndicate ( plus_sign ) ,
CircleIndicate ( minus_sign ) ,
)
self . wait ( )
2018-02-15 11:57:45 -08:00
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 )
2018-02-15 14:44:32 -08:00
self . play ( FadeIn ( mid_point ) , FadeIn ( question_mark ) )
2018-02-15 11:57:45 -08:00
self . wait ( )
self . play (
ApplyMethod ( mid_point . set_color , positive_color ) ,
ReplacementTransform ( question_mark , plus_sign_copy ) ,
)
self . play (
2018-02-15 14:44:32 -08:00
CircleIndicate ( plus_sign_copy ) ,
CircleIndicate ( minus_sign ) ,
2018-02-15 11:57:45 -08:00
)
self . wait ( )
self . play (
ApplyMethod ( mid_point . set_color , negative_color ) ,
ReplacementTransform ( plus_sign_copy , minus_sign_copy ) ,
)
self . play (
2018-02-15 14:44:32 -08:00
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 ) ,
2018-02-15 11:57:45 -08:00
)
2018-02-15 14:44:32 -08:00
pair_indicate ( left_interval [ 0 ] , right_interval [ 1 ] )
2018-02-15 11:57:45 -08:00
self . wait ( )
2018-03-20 16:57:06 -07:00
tiny_loop_func = scale_func ( plane_func_by_wind_spec ( ( - 1 , - 2 ) , ( 1 , 1 ) , ( 1 , 1 ) ) , 0.3 )
class TinyLoopScene ( ColorMappedByFuncScene ) :
2018-02-22 15:32:49 -08:00
CONFIG = {
2018-02-26 16:07:50 -08:00
" func " : tiny_loop_func ,
" show_num_plane " : False ,
2018-03-20 16:57:06 -07:00
" loop_point " : ORIGIN ,
" circle_scale " : 0.7
2018-02-22 15:32:49 -08:00
}
def construct ( self ) :
ColorMappedByFuncScene . construct ( self )
2018-02-28 19:48:39 -08:00
circle = cw_circle . copy ( )
2018-03-20 16:57:06 -07:00
circle . scale ( self . circle_scale )
circle . move_to ( self . loop_point )
2018-02-22 15:32:49 -08:00
self . play ( ShowCreation ( circle ) )
2018-03-20 16:57:06 -07:00
self . wait ( )
2018-02-22 15:32:49 -08:00
2018-03-20 16:57:06 -07:00
class TinyLoopInInputPlaneAroundNonZero ( TinyLoopScene ) :
CONFIG = {
" loop_point " : 0.5 * RIGHT
}
class TinyLoopInInputPlaneAroundZero ( TinyLoopScene ) :
CONFIG = {
" loop_point " : UP + RIGHT
}
class TinyLoopInOutputPlaneAroundNonZero ( TinyLoopInInputPlaneAroundNonZero ) :
CONFIG = {
" camera_class " : MappingCamera ,
" camera_config " : { " mapping_func " : point3d_func_from_plane_func ( tiny_loop_func ) } ,
" show_output " : True ,
" show_num_plane " : False ,
}
class TinyLoopInOutputPlaneAroundZero ( TinyLoopInInputPlaneAroundZero ) :
2018-02-22 15:32:49 -08:00
CONFIG = {
" camera_class " : MappingCamera ,
2018-02-28 19:48:39 -08:00
" camera_config " : { " mapping_func " : point3d_func_from_plane_func ( tiny_loop_func ) } ,
2018-02-22 15:32:49 -08:00
" show_output " : True ,
" show_num_plane " : False ,
}
2018-02-28 19:48:39 -08:00
class BorderOf2dRegionScene ( Scene ) :
def construct ( self ) :
num_plane = NumberPlane ( )
self . add ( num_plane )
points = standard_rect + 1.5 * UP + 2 * RIGHT
interior = Polygon ( * points , fill_color = neutral_color , fill_opacity = 1 , stroke_width = 0 )
self . play ( FadeIn ( interior ) )
border = Polygon ( * points , color = negative_color , stroke_width = border_stroke_width )
self . play ( ShowCreation ( border ) )
2018-07-11 11:38:59 -07:00
big_loop_no_zeros_func = lambda x_y5 : complex_to_pair ( np . exp ( complex ( 10 , x_y5 [ 1 ] * np . pi ) ) )
2018-02-28 19:48:39 -08:00
class BigLoopNoZeros ( ColorMappedObjectsScene ) :
CONFIG = {
" func " : big_loop_no_zeros_func
}
def construct ( self ) :
ColorMappedObjectsScene . construct ( self )
points = 3 * np . array ( [ UL , UR , DR , DL ] )
polygon = Polygon ( * points )
polygon . color_using_background_image ( self . background_image_file )
self . play ( ShowCreation ( polygon ) )
self . wait ( )
polygon2 = polygon . copy ( )
polygon2 . fill_opacity = 1
self . play ( FadeIn ( polygon2 ) )
self . wait ( )
class ExamplePlaneFunc ( ColorMappedByFuncScene ) :
CONFIG = {
" show_num_plane " : False ,
" func " : example_plane_func
}
def construct ( self ) :
ColorMappedByFuncScene . construct ( self )
radius = 0.5
2018-03-05 17:58:57 -08:00
def circle_point ( point ) :
circle = cw_circle . copy ( ) . scale ( radius ) . move_to ( point )
self . play ( ShowCreation ( circle ) )
return circle
def circle_spec ( spec ) :
point = spec [ 0 ] * RIGHT + spec [ 1 ] * UP
return circle_point ( point )
2018-02-28 19:48:39 -08:00
nonzero_point = ORIGIN # Manually chosen, not auto-synced with example_plane_func
2018-03-05 17:58:57 -08:00
nonzero_point_circle = circle_point ( nonzero_point )
2018-02-28 19:48:39 -08:00
self . wait ( )
2018-03-05 17:58:57 -08:00
self . play ( FadeOut ( nonzero_point_circle ) )
2018-02-28 19:48:39 -08:00
self . wait ( )
zero_circles = Group ( )
2018-03-05 17:58:57 -08:00
for spec in example_plane_func_spec :
zero_circles . add ( circle_spec ( spec ) )
2018-02-28 19:48:39 -08:00
self . wait ( )
# TODO: Fix the code in Fade to automatically propagate correctly
# to subobjects, even with special vectorized object handler.
# Also, remove the special handling from FadeOut, have it implemented
# solely through Fade.
#
# But for now, I'll just take care of this stuff myself here.
2018-03-05 17:58:57 -08:00
# self.play(*[FadeOut(zero_circle) for zero_circle in zero_circles])
self . play ( FadeOut ( zero_circles ) )
self . wait ( )
# We can reuse our nonzero point from before for "Output doesn't go through ever color"
# Do re-use in Premiere
# We can also re-use the first of our zero-circles for "Output does go through every color",
# but just in case it would be useful, here's another one, all on its own
specific_spec_index = 0
temp_circle = circle_spec ( example_plane_func_spec [ specific_spec_index ] )
self . play ( FadeOut ( temp_circle ) )
2018-02-28 19:48:39 -08:00
self . wait ( )
class PiWalkerExamplePlaneFunc ( PiWalkerRect ) :
CONFIG = {
" show_num_plane " : False ,
" func " : example_plane_func ,
# These are just manually entered, not
# automatically kept in sync with example_plane_func:
" start_x " : - 4 ,
" start_y " : 3 ,
" walk_width " : 8 ,
" walk_height " : 6 ,
}
2018-03-09 12:07:54 -08:00
class NoticeHowOnThisLoop ( PiWalkerRect ) :
CONFIG = {
" show_num_plane " : False ,
" func " : example_plane_func ,
# These are just manually entered, not
# automatically kept in sync with example_plane_func:
" start_x " : 0.5 ,
" start_y " : - 0.5 ,
" walk_width " : - 1 , # We trace from bottom-right clockwise on this one, to start at a red point
" walk_height " : - 1 ,
}
class ButOnThisLoopOverHere ( NoticeHowOnThisLoop ) :
CONFIG = {
# These are just manually entered, not
# automatically kept in sync with example_plane_func:
" start_x " : - 1 ,
" start_y " : 0 ,
" walk_width " : 1 ,
" walk_height " : 1 ,
}
2018-02-28 19:48:39 -08:00
class PiWalkerExamplePlaneFuncWithScaling ( PiWalkerExamplePlaneFunc ) :
CONFIG = {
" scale_arrows " : True ,
" display_size " : True ,
}
class TinyLoopOfBasicallySameColor ( PureColorMap ) :
def construct ( self ) :
PureColorMap . construct ( self )
radius = 0.5
circle = cw_circle . copy ( ) . scale ( radius ) . move_to ( UP + RIGHT )
self . play ( ShowCreation ( circle ) )
self . wait ( )
2018-07-11 11:38:59 -07:00
def uhOhFunc ( xxx_todo_changeme11 ) :
( x , y ) = xxx_todo_changeme11
2018-03-09 10:32:19 -08:00
x = - np . clip ( x , - 5 , 5 ) / 5
y = - np . clip ( y , - 3 , 3 ) / 3
2018-03-06 13:56:42 -08:00
alpha = 0.5 # Most things will return green
# These next three things should really be abstracted into some "Interpolated triangle" function
if x > = 0 and y > = x and y < = 1 :
alpha = interpolate ( 0.5 , 1 , y - x )
if x < 0 and y > = - 2 * x and y < = 1 :
alpha = interpolate ( 0.5 , 1 , y + 2 * x )
if x > = - 1 and y > = 2 * ( x + 1 ) and y < = 1 :
alpha = interpolate ( 0.5 , 0 , y - 2 * ( x + 1 ) )
return complex_to_pair ( 100 * np . exp ( complex ( 0 , TAU * ( 0.5 - alpha ) ) ) )
class UhOhFuncTest ( PureColorMap ) :
CONFIG = {
" func " : uhOhFunc
}
2018-03-05 17:58:57 -08:00
class UhOhScene ( EquationSolver2d ) :
CONFIG = {
2018-03-06 13:56:42 -08:00
" func " : uhOhFunc ,
2018-03-09 10:32:19 -08:00
" manual_wind_override " : ( 1 , None , ( 1 , None , ( 1 , None , None ) ) ) , # Tailored to UhOhFunc above
2018-03-05 17:58:57 -08:00
" show_winding_numbers " : False ,
2018-03-06 13:56:42 -08:00
" num_iterations " : 5 ,
}
2018-03-20 16:57:06 -07:00
class UhOhSceneWithWindingNumbers ( UhOhScene ) :
CONFIG = {
" show_winding_numbers " : True ,
}
class UhOhSceneWithWindingNumbersNoOverride ( UhOhSceneWithWindingNumbers ) :
CONFIG = {
" manual_wind_override " : None ,
" num_iterations " : 2
}
2018-03-06 13:56:42 -08:00
class UhOhSalientStill ( ColorMappedObjectsScene ) :
CONFIG = {
" func " : uhOhFunc
2018-03-05 17:58:57 -08:00
}
2018-03-06 13:56:42 -08:00
def construct ( self ) :
ColorMappedObjectsScene . construct ( self )
2018-03-06 16:03:00 -08:00
2018-03-06 13:56:42 -08:00
new_up = 3 * UP
new_left = 5 * LEFT
thin_line = Line ( UP , RIGHT , color = WHITE )
main_points = [ new_left + new_up , new_up , ORIGIN , new_left ]
polygon = Polygon ( * main_points , stroke_width = border_stroke_width )
thin_polygon = polygon . copy ( ) . match_style ( thin_line )
polygon . color_using_background_image ( self . background_image_file )
midline = Line ( new_up + 0.5 * new_left , 0.5 * new_left , stroke_width = border_stroke_width )
thin_midline = midline . copy ( ) . match_style ( thin_line )
midline . color_using_background_image ( self . background_image_file )
self . add ( polygon , midline )
self . wait ( )
everything_filler = FullScreenFadeRectangle ( fill_opacity = 1 )
everything_filler . color_using_background_image ( self . background_image_file )
thin_white_copy = Group ( thin_polygon , thin_midline )
self . play ( FadeIn ( everything_filler ) , FadeIn ( thin_white_copy ) )
self . wait ( )
2018-01-24 13:11:44 -08:00
# TODO: Brouwer's fixed point theorem visuals
2018-01-30 13:55:59 -08:00
# class BFTScene(Scene):
2018-01-24 13:11:44 -08:00
# TODO: Pi creatures wide-eyed in amazement
#################
# TODOs, from easiest to hardest:
2018-01-29 13:39:56 -08:00
# Minor fiddling with little things in each animation; placements, colors, timing, text
2018-01-24 13:11:44 -08:00
2018-01-29 13:39:56 -08:00
# Initial odometer scene (simple once previous Pi walker scene is decided upon)
2018-01-24 13:11:44 -08:00
2018-01-29 13:39:56 -08:00
# Writing new Pi walker scenes by parametrizing general template
2018-01-24 13:11:44 -08:00
2018-02-01 16:33:03 -08:00
# (All the above are basically trivial tinkering at this point)
2018-01-26 12:26:30 -08:00
# ----
# Pi creature emotion stuff
2018-01-24 13:11:44 -08:00
# BFT visuals
# Borsuk-Ulam visuals
2018-01-31 17:17:58 -08:00
####################
2018-02-06 12:44:38 -08:00
# Random test scenes and test functions go here:
2018-07-11 11:38:59 -07:00
def rect_to_circle ( xxx_todo_changeme12 ) :
( x , y , z ) = xxx_todo_changeme12
2018-02-06 12:44:38 -08:00
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 )
2018-02-01 16:33:03 -08:00
class MapPiWalkerRect ( PiWalkerRect ) :
CONFIG = {
" camera_class " : MappingCamera ,
2018-02-06 12:44:38 -08:00
" camera_config " : { " mapping_func " : rect_to_circle } ,
2018-02-22 15:32:49 -08:00
" show_output " : True
2018-02-01 16:33:03 -08:00
}
2018-01-31 17:17:58 -08:00
2018-02-06 12:44:38 -08:00
class ShowBack ( PiWalkerRect ) :
CONFIG = {
2018-02-28 19:48:39 -08:00
" func " : plane_func_by_wind_spec ( ( 1 , 2 ) , ( - 1 , 1.5 ) , ( - 1 , 1.5 ) )
2018-02-06 12:44:38 -08:00
}
2018-02-28 19:48:39 -08:00
class PiWalkerOdometerTest ( PiWalkerExamplePlaneFunc ) :
CONFIG = {
" display_odometer " : True
}
class PiWalkerFancyLineTest ( PiWalkerExamplePlaneFunc ) :
CONFIG = {
" color_foreground_not_background " : True
}
2018-03-09 10:32:19 -08:00
class NotFoundScene ( Scene ) :
def construct ( self ) :
self . add ( TextMobject ( " SCENE NOT FOUND! " ) )
self . wait ( )
criticalStripYScale = 100
2018-03-30 11:25:37 -07:00
criticalStrip = Axes ( x_min = - 0.5 , x_max = 1.5 , x_axis_config = { " unit_size " : FRAME_X_RADIUS ,
2018-03-09 10:32:19 -08:00
" number_at_center " : 0.5 } ,
y_min = - criticalStripYScale , y_max = criticalStripYScale ,
2018-03-30 11:25:37 -07:00
y_axis_config = { " unit_size " : fdiv ( FRAME_Y_RADIUS , criticalStripYScale ) } )
2018-03-09 10:32:19 -08:00
class ZetaViz ( PureColorMap ) :
CONFIG = {
" func " : plane_zeta ,
#"num_plane" : criticalStrip,
" show_num_plane " : True
}
2018-03-20 16:57:06 -07:00
class TopLabel ( Scene ) :
CONFIG = {
" text " : " Text "
}
def construct ( self ) :
label = TextMobject ( self . text )
label . move_to ( 3 * UP )
self . add ( label )
self . wait ( )
# This is a giant hack that doesn't handle rev wrap-around correctly; should use
# make_alpha_winder instead
class SpecifiedWinder ( PiWalker ) :
CONFIG = {
" start_x " : 0 ,
" start_y " : 0 ,
" x_wind " : 1 , # Assumed positive
" y_wind " : 1 , # Assumed positive
" step_size " : 0.1
}
def setup ( self ) :
rev_func = lambda p : point_to_rev ( self . func ( p ) )
start_pos = np . array ( ( self . start_x , self . start_y ) )
cur_pos = start_pos . copy ( )
start_rev = rev_func ( start_pos )
mid_rev = start_rev
while ( abs ( mid_rev - start_rev ) < self . x_wind ) :
cur_pos + = ( self . step_size , 0 )
mid_rev = rev_func ( cur_pos )
2018-07-11 11:38:59 -07:00
print ( " Reached " , cur_pos , " , with rev " , mid_rev - start_rev )
2018-03-20 16:57:06 -07:00
mid_pos = cur_pos . copy ( )
end_rev = mid_rev
while ( abs ( end_rev - mid_rev ) < self . y_wind ) :
cur_pos - = ( 0 , self . step_size )
end_rev = rev_func ( cur_pos )
end_pos = cur_pos . copy ( )
2018-07-11 11:38:59 -07:00
print ( " Reached " , cur_pos , " , with rev " , end_rev - mid_rev )
2018-03-20 16:57:06 -07:00
self . walk_coords = [ start_pos , mid_pos , end_pos ]
2018-07-11 11:38:59 -07:00
print ( " Walk coords: " , self . walk_coords )
2018-03-20 16:57:06 -07:00
PiWalker . setup ( self )
class OneFifthTwoFifthWinder ( SpecifiedWinder ) :
CONFIG = {
" func " : example_plane_func ,
" start_x " : - 2.0 ,
" start_y " : 1.0 ,
" x_wind " : 0.2 ,
" y_wind " : 0.2 ,
" step_size " : 0.01 ,
" show_num_plane " : False ,
" step_run_time " : 6 ,
2018-05-09 18:21:25 +02:00
" num_decimal_places " : 2 ,
2018-03-22 11:54:08 -07:00
}
class OneFifthOneFifthWinderWithReset ( OneFifthTwoFifthWinder ) :
CONFIG = {
" wind_reset_indices " : [ 1 ]
2018-03-20 16:57:06 -07:00
}
class OneFifthTwoFifthWinderOdometer ( OneFifthTwoFifthWinder ) :
CONFIG = {
" display_odometer " : True ,
}
class ForwardBackWalker ( PiWalker ) :
CONFIG = {
" func " : example_plane_func ,
" walk_coords " : [ np . array ( ( - 2 , 1 ) ) , np . array ( ( 1 , 1 ) ) ] ,
" step_run_time " : 3 ,
}
class ForwardBackWalkerOdometer ( ForwardBackWalker ) :
CONFIG = {
" display_odometer " : True ,
}
class PureOdometerBackground ( OdometerScene ) :
CONFIG = {
" pure_odometer_background " : True
}
class CWColorWalk ( PiWalkerRect ) :
CONFIG = {
" func " : example_plane_func ,
" start_x " : example_plane_func_spec [ 0 ] [ 0 ] - 1 ,
" start_y " : example_plane_func_spec [ 0 ] [ 1 ] + 1 ,
" walk_width " : 2 ,
" walk_height " : 2 ,
" draw_lines " : False ,
2018-03-22 11:54:08 -07:00
" display_wind " : False ,
" step_run_time " : 2
2018-03-20 16:57:06 -07:00
}
class CWColorWalkOdometer ( CWColorWalk ) :
CONFIG = {
" display_odometer " : True ,
}
class CCWColorWalk ( CWColorWalk ) :
CONFIG = {
" start_x " : example_plane_func_spec [ 2 ] [ 0 ] - 1 ,
" start_y " : example_plane_func_spec [ 2 ] [ 1 ] + 1 ,
}
class CCWColorWalkOdometer ( CCWColorWalk ) :
CONFIG = {
" display_odometer " : True ,
}
class ThreeTurnWalker ( PiWalkerRect ) :
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : c * * 3 * complex ( 1 , 1 ) * * 3 ) ,
2018-03-22 11:54:08 -07:00
" double_up " : True ,
" wind_reset_indices " : [ 4 ]
2018-03-20 16:57:06 -07:00
}
class ThreeTurnWalkerOdometer ( ThreeTurnWalker ) :
CONFIG = {
" display_odometer " : True ,
}
class FourTurnWalker ( PiWalkerRect ) :
CONFIG = {
" func " : plane_func_by_wind_spec ( ( 0 , 0 , 4 ) )
}
class FourTurnWalkerOdometer ( FourTurnWalker ) :
CONFIG = {
" display_odometer " : True ,
}
class OneTurnWalker ( PiWalkerRect ) :
CONFIG = {
" func " : plane_func_from_complex_func ( lambda c : np . exp ( c ) + c )
}
class OneTurnWalkerOdometer ( OneTurnWalker ) :
CONFIG = {
" display_odometer " : True ,
}
class ZeroTurnWalker ( PiWalkerRect ) :
CONFIG = {
" func " : plane_func_by_wind_spec ( ( 2 , 2 , 1 ) , ( - 1 , 2 , - 1 ) )
}
class ZeroTurnWalkerOdometer ( ZeroTurnWalker ) :
CONFIG = {
" display_odometer " : True ,
}
class NegOneTurnWalker ( PiWalkerRect ) :
CONFIG = {
2018-03-22 11:54:08 -07:00
" step_run_time " : 2 ,
2018-03-20 16:57:06 -07:00
" func " : plane_func_by_wind_spec ( ( 0 , 0 , - 1 ) )
}
class NegOneTurnWalkerOdometer ( NegOneTurnWalker ) :
CONFIG = {
" display_odometer " : True ,
}
2018-02-01 16:33:03 -08:00
# FIN