diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index aebfd650..bd9bb1e2 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -33,15 +33,60 @@ from topics.graph_scene import * # (and it will be done, but first I'll figure out what I'm doing with all this...) # -SR -positive_color = GREEN -negative_color = RED -neutral_color = YELLOW +def rev_to_rgba(alpha): + alpha = (0.5 - alpha) % 1 # For convenience, to go CW from red on left instead of CCW from right + # 0 is red, 1/6 is yellow, 1/3 is green, 2/3 is blue + hue_list = [0, 0.5/6.0, 1/6.0, 1.1/6.0, 2/6.0, 3/6.0, 4/6.0, 5/6.0] + num_hues = len(hue_list) + start_index = int(np.floor(num_hues * alpha)) % num_hues + end_index = (start_index + 1) % num_hues + beta = (alpha % (1.0/num_hues)) * num_hues + + start_hue = hue_list[start_index] + end_hue = hue_list[end_index] + if end_hue < start_hue: + end_hue = end_hue + 1 + hue = interpolate(start_hue, end_hue, beta) + + return color_to_rgba(Color(hue = hue, saturation = 1, luminance = 0.5)) + + # alpha = alpha % 1 + # colors = colorslist + # num_colors = len(colors) + # beta = (alpha % (1.0/num_colors)) * num_colors + # start_index = int(np.floor(num_colors * alpha)) % num_colors + # end_index = (start_index + 1) % num_colors + + # return interpolate(colors[start_index], colors[end_index], beta) + +def rev_to_color(alpha): + return rgba_to_color(rev_to_rgba(alpha)) + +def point_to_rev((x, y), allow_origin = False): + # Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to + # design choices in the underlying atan2 library call, but for our purposes, this is + # illegitimate, and all winding number calculations must be set up to avoid this + if not(allow_origin) and (x, y) == (0, 0): + print "Error! Angle of (0, 0) computed!" + return + return fdiv(np.arctan2(y, x), TAU) + +def point_to_rgba(point): + rev = point_to_rev(point, allow_origin = True) + rgba = rev_to_rgba(rev) + base_size = np.sqrt(point[0]**2 + point[1]**2) + rescaled_size = np.sqrt(base_size/(base_size + 1)) + return rgba * rescaled_size + +positive_color = rev_to_color(0) +negative_color = rev_to_color(0.5) +neutral_color = rev_to_color(0.25) class EquationSolver1d(GraphScene, ZoomedScene): CONFIG = { "camera_config" : { - #"use_z_coordinate_for_display_order": True, + "use_z_coordinate_for_display_order": True, }, "func" : lambda x : x, "targetX" : 0, @@ -140,9 +185,9 @@ class EquationSolver1d(GraphScene, ZoomedScene): lowerDotPoint = self.input_to_graph_point(lowerX, self.graph) lowerDotXPoint = self.coords_to_point(lowerX, 0) lowerDotYPoint = self.coords_to_point(0, self.func(lowerX)) - lowerDot = Dot(lowerDotPoint, color = negative_color) + lowerDot = Dot(lowerDotPoint + OUT, color = negative_color) upperDotPoint = self.input_to_graph_point(upperX, self.graph) - upperDot = Dot(upperDotPoint, color = positive_color) + upperDot = Dot(upperDotPoint + OUT, color = positive_color) upperDotXPoint = self.coords_to_point(upperX, 0) upperDotYPoint = self.coords_to_point(0, self.func(upperX)) @@ -154,15 +199,22 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.add(xBraces, yBraces, lowerDot, upperDot) + x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = neutral_color) + self.add(x_guess_line) + lowerGroup = Group( lowerDot, leftBrace, downBrace, - lowerXLine, lowerYLine) + lowerXLine, lowerYLine, + x_guess_line + ) upperGroup = Group( upperDot, rightBrace, upBrace, - upperXLine, upperYLine) + upperXLine, upperYLine, + x_guess_line + ) initialLowerXDot = Dot(lowerDotXPoint, color = negative_color) initialUpperXDot = Dot(upperDotXPoint, color = positive_color) @@ -181,9 +233,9 @@ class EquationSolver1d(GraphScene, ZoomedScene): upperDot.scale_in_place, inverseZoomFactor) - def makeUpdater(xAtStart): + def makeUpdater(xAtStart, fixed_guess_x): def updater(group, alpha): - dot, xBrace, yBrace, xLine, yLine = group + dot, xBrace, yBrace, xLine, yLine, guess_line = group newX = interpolate(xAtStart, midX, alpha) newY = self.func(newX) graphPoint = self.input_to_graph_point(newX, @@ -195,15 +247,20 @@ class EquationSolver1d(GraphScene, ZoomedScene): yBrace.move_to(yAxisPoint) xLine.put_start_and_end_on(xAxisPoint, graphPoint) yLine.put_start_and_end_on(yAxisPoint, graphPoint) + fixed_guess_point = self.coords_to_point(fixed_guess_x, 0) + guess_line.put_start_and_end_on(xAxisPoint, fixed_guess_point) return group return updater midX = (lowerX + upperX)/float(2) midY = self.func(midX) + in_negative_branch = midY < self.targetY + sign_color = negative_color if in_negative_branch else positive_color midCoords = self.coords_to_point(midX, midY) midColor = neutral_color - midXPoint = Dot(self.coords_to_point(midX, 0), color = midColor) + # Hm... even the z buffer isn't helping keep this above x_guess_line + midXPoint = Dot(self.coords_to_point(midX, 0) + OUT, color = midColor) x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor) x_guess_label_num = DecimalNumber(midX, fill_color = midColor) @@ -211,7 +268,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): x_guess_label_caption.next_to(x_guess_label_num, LEFT) x_guess_label = Group(x_guess_label_caption, x_guess_label_num) y_guess_label_caption = TextMobject(", y = ", fill_color = midColor) - y_guess_label_num = DecimalNumber(midY, fill_color = midColor) + y_guess_label_num = DecimalNumber(midY, fill_color = sign_color) y_guess_label_caption.next_to(x_guess_label_num, RIGHT) y_guess_label_num.next_to(y_guess_label_caption, RIGHT) y_guess_label = Group(y_guess_label_caption, y_guess_label_num) @@ -222,35 +279,43 @@ class EquationSolver1d(GraphScene, ZoomedScene): ReplacementTransform(rightBrace.copy(), midXPoint), FadeIn(x_guess_label)) - midXLine = Line(self.coords_to_point(midX, 0), midCoords, color = midColor) + midXLine = DashedLine(self.coords_to_point(midX, 0), midCoords, color = midColor) self.play(ShowCreation(midXLine)) - midDot = Dot(midCoords, color = midColor) + midDot = Dot(midCoords, color = sign_color) if(self.iteration_at_which_to_start_zoom != None and i >= self.iteration_at_which_to_start_zoom): midDot.scale_in_place(inverseZoomFactor) self.add(midDot) - midYLine = Line(midCoords, self.coords_to_point(0, midY), color = midColor) + midYLine = DashedLine(midCoords, self.coords_to_point(0, midY), color = sign_color) self.play( ShowCreation(midYLine), - FadeIn(y_guess_label)) - midYPoint = Dot(self.coords_to_point(0, midY), color = midColor) + FadeIn(y_guess_label), + ApplyMethod(midXPoint.set_color, sign_color), + ApplyMethod(midXLine.set_color, sign_color)) + midYPoint = Dot(self.coords_to_point(0, midY), color = sign_color) self.add(midYPoint) - if midY < self.targetY: + if in_negative_branch: self.play( - UpdateFromAlphaFunc(lowerGroup, makeUpdater(lowerX)), + UpdateFromAlphaFunc(lowerGroup, + makeUpdater(lowerX, + fixed_guess_x = upperX + ) + ), FadeOut(guess_labels), - ApplyMethod(midXPoint.set_color, negative_color), - ApplyMethod(midYPoint.set_color, negative_color)) + ) lowerX = midX lowerY = midY else: self.play( - UpdateFromAlphaFunc(upperGroup, makeUpdater(upperX)), + UpdateFromAlphaFunc(upperGroup, + makeUpdater(upperX, + fixed_guess_x = lowerX + ) + ), FadeOut(guess_labels), - ApplyMethod(midXPoint.set_color, positive_color), - ApplyMethod(midYPoint.set_color, positive_color)) + ) upperX = midX upperY = midY #mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected? @@ -262,37 +327,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.drawGraph() self.solveEquation() -colorslist = map(color_to_rgba, ["#FF0000", "#FFFF00", "#00FF00", "#0000FF"]) - -def rev_to_rgba(alpha): - alpha = alpha % 1 - colors = colorslist - num_colors = len(colors) - beta = (alpha % (1.0/num_colors)) * num_colors - start_index = int(np.floor(num_colors * alpha)) % num_colors - end_index = (start_index + 1) % num_colors - - return interpolate(colors[start_index], colors[end_index], beta) - -def rev_to_color(alpha): - return rgba_to_color(rev_to_rgba(alpha)) - -def point_to_rev((x, y), allow_origin = False): - # Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to - # design choices in the underlying atan2 library call, but for our purposes, this is - # illegitimate, and all winding number calculations must be set up to avoid this - if not(allow_origin) and (x, y) == (0, 0): - print "Error! Angle of (0, 0) computed!" - return - return fdiv(np.arctan2(y, x), TAU) - -def point_to_rgba(point): - rev = point_to_rev(point, allow_origin = True) - rgba = rev_to_rgba(rev) - base_size = np.sqrt(point[0]**2 + point[1]**2) - rescaled_size = np.sqrt(base_size/(base_size + 1)) - return rgba * rescaled_size - # Returns the value with the same fractional component as x, closest to m def resit_near(x, m): frac_diff = (x - m) % 1 @@ -520,6 +554,11 @@ class ColorMappedByFuncScene(Scene): ) ) +class PureColorMap(ColorMappedByFuncScene): + CONFIG = { + "show_num_plane" : False + } + class ColorMappedByFuncStill(ColorMappedByFuncScene): def construct(self): ColorMappedByFuncScene.construct(self) @@ -896,6 +935,37 @@ class SecondSqrtScene(FirstSqrtScene): # TODO: Pi creatures intrigued +class RewriteEquation(Scene): + def construct(self): + # Can maybe fitz around with smoothening the transform, so just = goes to - and new stuff + # is added at the right end, while things re-center + f_old = TexMobject("f(x)") + f_new = f_old.copy() + equals_old = TexMobject("=") + equals_old_2 = equals_old.copy() + equals_new = equals_old.copy() + g_old = TexMobject("g(x)") + g_new = g_old.copy() + minus_new = TexMobject("-") + zero_new = TexMobject("0") + f_old.next_to(equals_old, LEFT) + g_old.next_to(equals_old, RIGHT) + minus_new.next_to(g_new, LEFT) + f_new.next_to(minus_new, LEFT) + equals_new.next_to(g_new, RIGHT) + zero_new.next_to(equals_new, RIGHT) + + self.add(f_old, equals_old, equals_old_2, g_old) + self.wait() + self.play( + ReplacementTransform(f_old, f_new), + ReplacementTransform(equals_old, equals_new), + ReplacementTransform(g_old, g_new), + ReplacementTransform(equals_old_2, minus_new), + ShowCreation(zero_new), + ) + self.wait() + class SignsExplanation(Scene): def construct(self): num_line = NumberLine(stroke_width = 1) @@ -911,12 +981,12 @@ class SignsExplanation(Scene): num_line.number_to_point(0), num_line.number_to_point(pos_num), buff = 0, - color = YELLOW) + color = positive_color) neg_arrow = Arrow( num_line.number_to_point(0), num_line.number_to_point(neg_num), buff = 0, - color = RED) + color = negative_color) #num_line.add_numbers(pos_num) self.play(ShowCreation(pos_arrow)) @@ -1300,6 +1370,55 @@ class DiffOdometer(OdometerScene): "biased_display_start" : 0 } +class CombineInterval(Scene): + def construct(self): + plus_sign = TexMobject("+", fill_color = positive_color) + minus_sign = TexMobject("-", fill_color = negative_color) + + left_point = Dot(LEFT, color = positive_color) + right_point = Dot(RIGHT, color = negative_color) + line1 = Line(LEFT, RIGHT) + interval1 = Group(line1, left_point, right_point) + + plus_sign.next_to(left_point, UP) + minus_sign.next_to(right_point, UP) + + self.add(interval1, plus_sign, minus_sign) + self.wait() + + mid_point = Dot(ORIGIN, color = GREY) + + question_mark = TexMobject("?", fill_color = GREY) + plus_sign_copy = plus_sign.copy() + minus_sign_copy = minus_sign.copy() + new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy) + for sign in new_signs: sign.next_to(mid_point, UP) + + self.play(ShowCreation(mid_point), ShowCreation(question_mark)) + self.wait() + + self.play( + ApplyMethod(mid_point.set_color, positive_color), + ReplacementTransform(question_mark, plus_sign_copy), + ) + self.play( + HighlightCircle(plus_sign_copy), + HighlightCircle(minus_sign), + ) + + self.wait() + + self.play( + ApplyMethod(mid_point.set_color, negative_color), + ReplacementTransform(plus_sign_copy, minus_sign_copy), + ) + self.play( + HighlightCircle(minus_sign_copy), + HighlightCircle(plus_sign), + ) + + self.wait() + # TODO: Brouwer's fixed point theorem visuals # class BFTScene(Scene): @@ -1351,4 +1470,10 @@ class ShowBack(PiWalkerRect): "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)) } +class Diagnostic(Scene): + def construct(self): + testList = map( (lambda n : (n, rev_to_rgba(n))), [0, 0.25, 0.5, 0.9]) + print "rev_to_rgbas", testList + self.wait() + # FIN \ No newline at end of file diff --git a/animation/transform.py b/animation/transform.py index 4ee132a5..ff13a388 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -8,7 +8,7 @@ from helpers import * from animation import Animation from mobject import Mobject, Point, VMobject, Group -from topics.geometry import Dot +from topics.geometry import Dot, Circle class Transform(Animation): CONFIG = { @@ -214,6 +214,17 @@ class Indicate(Transform): target.highlight(self.color) Transform.__init__(self, mobject, target, **kwargs) +class HighlightCircle(Indicate): + CONFIG = { + "rate_func" : squish_rate_func(there_and_back, 0, 0.8), + "remover" : True + } + def __init__(self, mobject, **kwargs): + digest_config(self, kwargs) + circle = Circle(color = self.color, **kwargs) + circle.surround(mobject) + Indicate.__init__(self, circle, **kwargs) + class Rotate(ApplyMethod): CONFIG = { "in_place" : False,