diff --git a/camera/camera.py b/camera/camera.py index ef988f89..f599eb09 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -394,7 +394,7 @@ class Camera(object): y0 = max(y0, 0) image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1] else: - # Alternate (slower) tactice if image is tilted + # Alternate (slower) tactic if image is tilted # List of all coordinates of pixels, given as (x, y), # which matches the return type of points_to_pixel_coords, # even though np.array indexing naturally happens as (y, x) @@ -618,7 +618,7 @@ class MovingCamera(Camera): def capture_mobjects(self, *args, **kwargs): self.space_center = self.mobject.get_center() - self.realign_space_shape() + self.realign_space_shape() Camera.capture_mobjects(self, *args, **kwargs) def realign_space_shape(self): diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index e4a74382..09d5ed4b 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -29,6 +29,8 @@ class ImageMobject(Mobject): else: self.pixel_array = np.array(filename_or_array) self.change_to_rgba_array() + if self.invert: + self.pixel_array[:,:,:3] = 255-self.pixel_array[:,:,:3] Mobject.__init__(self, **kwargs) def change_to_rgba_array(self): diff --git a/active_projects/fourier.py b/old_projects/fourier.py similarity index 100% rename from active_projects/fourier.py rename to old_projects/fourier.py diff --git a/active_projects/uncertainty.py b/old_projects/uncertainty.py similarity index 56% rename from active_projects/uncertainty.py rename to old_projects/uncertainty.py index 7afecb48..76b0c120 100644 --- a/active_projects/uncertainty.py +++ b/old_projects/uncertainty.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from helpers import * import scipy @@ -21,6 +23,7 @@ from topics.common_scenes import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene from scene.zoomed_scene import * +from scene.moving_camera_scene import * from camera import Camera from mobject import * from mobject.image_mobject import * @@ -36,6 +39,40 @@ from active_projects.fourier import * FREQUENCY_COLOR = RED USE_ALMOST_FOURIER_BY_DEFAULT = False +class ValueTracker(VectorizedPoint): + """ + Note meant to be displayed. Instead the position encodes some + number, often one which another animation or continual_animation + uses for its update function, and by treating it as a mobject it can + still be animated and manipulated just like anything else. + """ + def __init__(self, value = 0, **kwargs): + VectorizedPoint.__init__(self, **kwargs) + self.set_value(value) + + def get_value(self): + return self.get_center()[0] + + def set_value(self, value): + self.move_to(value*RIGHT) + return self + + def increment_value(self, d_value): + self.set_value(self.get_value() + d_value) + +class ExponentialValueTracker(ValueTracker): + """ + Operates just like ValueTracker, except it encodes the value as the + exponential of a position coordinate, which changes how interpolation + behaves + """ + def get_value(self): + return np.exp(self.get_center()[0]) + + def set_value(self, value): + self.move_to(np.log(value)*RIGHT) + return self + class GaussianDistributionWrapper(Line): """ This is meant to encode a 2d normal distribution as @@ -79,7 +116,9 @@ class ProbabalisticMobjectCloud(ContinualAnimation): CONFIG = { "fill_opacity" : 0.25, "n_copies" : 100, - "gaussian_distribution_wrapper_config" : {} + "gaussian_distribution_wrapper_config" : {}, + "time_per_change" : 1./60, + "start_up_time" : 0, } def __init__(self, prototype, **kwargs): digest_config(self, kwargs) @@ -89,13 +128,20 @@ class ProbabalisticMobjectCloud(ContinualAnimation): self.gaussian_distribution_wrapper = GaussianDistributionWrapper( **self.gaussian_distribution_wrapper_config ) + self.time_since_last_change = np.inf group = VGroup(*[ prototype.copy().set_fill(opacity = fill_opacity) for x in range(self.n_copies) ]) ContinualAnimation.__init__(self, group, **kwargs) + self.update_mobject(0) def update_mobject(self, dt): + self.time_since_last_change += dt + if self.time_since_last_change < self.time_per_change: + return + self.time_since_last_change = 0 + group = self.mobject points = self.gaussian_distribution_wrapper.get_random_points(len(group)) for mob, point in zip(group, points): @@ -420,7 +466,8 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): class FourierTradeoff(Scene): CONFIG = { "show_text" : True, - "complex_to_real_func" : abs, + "complex_to_real_func" : lambda z : z.real, + "widths" : [6, 0.02, 1], } def construct(self): #Setup axes @@ -447,8 +494,8 @@ class FourierTradeoff(Scene): x_min = 0, x_max = 8, x_axis_config = {"unit_size" : 1.5}, - y_min = 0, - y_max = 0.1, + y_min = -0.025, + y_max = 0.075, y_axis_config = { "unit_size" : 30, "tick_frequency" : 0.025, @@ -470,13 +517,12 @@ class FourierTradeoff(Scene): # Graph information #x-coordinate of this point determines width of wave_packet graph - width_tracker = VectorizedPoint(0.5*RIGHT) - def get_width(): - return width_tracker.get_center()[0] + width_tracker = ExponentialValueTracker(0.5) + get_width = width_tracker.get_value def get_wave_packet_function(): factor = 1./get_width() - return lambda t : np.sqrt(factor)*np.cos(4*TAU*t)*np.exp(-factor*(t-time_mean)**2) + return lambda t : (factor**0.25)*np.cos(4*TAU*t)*np.exp(-factor*(t-time_mean)**2) def get_wave_packet(): graph = time_axes.get_graph( @@ -515,7 +561,7 @@ class FourierTradeoff(Scene): ), color = FREQUENCY_COLOR, ) - fourier_words = TextMobject("$|$Fourier Transform$|$") + fourier_words = TextMobject("Fourier Transform") fourier_words.next_to(arrow, LEFT, buff = MED_LARGE_BUFF) sub_words = TextMobject("(To be explained shortly)") sub_words.highlight(BLUE) @@ -536,9 +582,9 @@ class FourierTradeoff(Scene): self.play(*anims) # self.play(FadeOut(arrow)) self.wait() - for width in 6, 0.02, 1: + for width in self.widths: self.play( - width_tracker.move_to, width*RIGHT, + width_tracker.set_value, width, wave_packet_update, fourier_graph_update, run_time = 3 @@ -1023,10 +1069,9 @@ class VariousMusicalNotes(Scene): # x-coordinate of this point represents log(a) # where the bell curve component of the signal # is exp(-a*(x**2)) - graph_width_tracker = VectorizedPoint() - graph_width_tracker.move_to(np.log(2)*RIGHT) + graph_width_tracker = ExponentialValueTracker(1) def get_graph(): - a = np.exp(graph_width_tracker.get_center()[0]) + a = graph_width_tracker.get_value() return FunctionGraph( lambda x : np.exp(-a*x**2)*np.sin(freq*x)-0.5, num_anchor_points = 500, @@ -1038,26 +1083,25 @@ class VariousMusicalNotes(Scene): def change_width_anim(width, **kwargs): a = 2.0/(width**2) return AnimationGroup( - ApplyMethod( - graph_width_tracker.move_to, - np.log(a)*RIGHT - ), + ApplyMethod(graph_width_tracker.set_value, a), graph_update_anim, **kwargs ) + change_width_anim(SPACE_WIDTH).update(1) + graph_update_anim.update(0) phrases = [ TextMobject(*words.split(" ")) for words in [ + "Very clear frequency", "Less clear frequency", "Extremely unclear frequency", - "Very clear frequency", ] ] #Show graphs and phrases - widths = [1, 0.2, SPACE_WIDTH] + widths = [SPACE_WIDTH, 1, 0.2] for width, phrase in zip(widths, phrases): brace = Brace(Line(LEFT, RIGHT), UP) brace.stretch(width, 0) @@ -1120,7 +1164,7 @@ class VariousMusicalNotes(Scene): ) self.play( Write(short_signal_words), - change_width_anim(widths[1]) + change_width_anim(widths[2]) ) self.play( long_graph.stretch, 0.35, 0, @@ -1131,7 +1175,7 @@ class VariousMusicalNotes(Scene): self.wait() self.play( Write(long_signal_words), - change_width_anim(widths[2]), + change_width_anim(widths[0]), ) self.play( long_graph.stretch, 0.95, 0, @@ -1141,6 +1185,27 @@ class VariousMusicalNotes(Scene): ) self.wait() +class CrossOutDefinitenessAndCertainty(TeacherStudentsScene): + def construct(self): + words = VGroup( + TextMobject("Definiteness"), + TextMobject("Certainty"), + ) + words.arrange_submobjects(DOWN) + words.next_to(self.teacher, UP+LEFT) + crosses = VGroup(*map(Cross, words)) + + self.add(words) + self.play( + self.teacher.change, "sassy", + ShowCreation(crosses[0]) + ) + self.play( + self.get_student_changes(*3*["erm"]), + ShowCreation(crosses[1]) + ) + self.wait(2) + class BringInFourierTranform(TeacherStudentsScene): def construct(self): fourier = TextMobject("Fourier") @@ -1297,8 +1362,7 @@ class FourierRecapScene(DrawFrequencyPlot): self.time_graph, draw_polarized_graph = False ), - rate_func = lambda t : 0.3*t, - run_time = 5 + run_time = 10 ) self.wait() @@ -1392,6 +1456,14 @@ class FourierRecapScene(DrawFrequencyPlot): graph.highlight(YELLOW) return graph +class RealPartOfInsert(Scene): + def construct(self): + words = TextMobject("(Real part of the)") + words.highlight(RED) + self.add(words) + self.play(Write(words)) + self.wait(5) + class CenterOfMassDescription(FourierRecapScene): def construct(self): self.remove(self.pi_creature) @@ -1647,10 +1719,36 @@ class LongAndShortSignalsInWindingMachine(FourierRecapScene): dot.arrow = arrow return dots +class FocusRectangleInsert(FourierRecapScene): + CONFIG = { + "target_width" : 0.5 + } + def construct(self): + self.setup_axes() + self.clear() + point = self.frequency_axes.coords_to_point(5, 0.25) + rect = ScreenRectangle(height = 2.1*SPACE_HEIGHT) + rect.set_stroke(YELLOW, 2) + self.add(rect) + self.wait() + self.play( + rect.stretch_to_fit_width, self.target_width, + rect.stretch_to_fit_height, 1.5, + rect.move_to, point, + run_time = 2 + ) + self.wait(3) + +class BroadPeakFocusRectangleInsert(FocusRectangleInsert): + CONFIG = { + "target_width" : 1.5, + } + class CleanerFourierTradeoff(FourierTradeoff): CONFIG = { "show_text" : False, "complex_to_real_func" : lambda z : z.real, + "widths" : [0.02, 6, 1], } class MentionDopplerRadar(TeacherStudentsScene): @@ -2056,6 +2154,31 @@ class IntroduceDopplerRadar(Scene): sum_graph.background_image_file = "blue_yellow_gradient" return pulse_graph, echo_graph, sum_graph +class DopplerFormulaInsert(Scene): + def construct(self): + formula = TexMobject( + "f_{\\text{echo}", "=", + "\\left(1 + \\frac{v}{c}\\right)", + "f_{\\text{pulse}}" + ) + formula[0].highlight(BLUE) + formula[3].highlight(YELLOW) + + randy = Randolph(color = BLUE_C) + formula.scale(1.5) + formula.next_to(randy, UP+LEFT) + formula.shift_onto_screen() + + self.add(randy) + self.play( + LaggedStart(FadeIn, formula), + randy.change, "pondering", randy.get_bottom(), + ) + self.play(Blink(randy)) + self.wait(2) + self.play(Blink(randy)) + self.wait() + class MentionPRFNuance(TeacherStudentsScene): def construct(self): title = TextMobject( @@ -2761,7 +2884,1959 @@ class AmbiguityInLongEchos(IntroduceDopplerRadar, PiCreatureScene): class SummarizeFourierTradeoffForDoppler(Scene): def construct(self): - pass + time_axes = Axes( + x_min = 0, x_max = 12, + y_min = -0.5, y_max = 1, + ) + time_axes.center().to_edge(UP, buff = LARGE_BUFF) + frequency_axes = time_axes.copy() + frequency_axes.next_to(time_axes, DOWN, buff = 2) + time_label = TextMobject("Time") + frequency_label = TextMobject("Frequency") + for label, axes in (time_label, time_axes), (frequency_label, frequency_axes): + label.next_to(axes.get_right(), UP, SMALL_BUFF) + axes.add(label) + frequency_label.shift_onto_screen() + title = TextMobject("Fourier Trade-off") + title.next_to(time_axes, DOWN) + self.add(title) + + + #Position determines log of scale value for exponentials + a_mob = VectorizedPoint() + x_values = [3, 5, 6, 7, 8] + v_values = [5, 5.5, 5.75, 6.5, 7] + def get_top_graphs(): + a = np.exp(a_mob.get_center()[0]) + graphs = VGroup(*[ + time_axes.get_graph(lambda t : np.exp(-5*a*(t-x)**2)) + for x in x_values + ]) + graphs.highlight(WHITE) + graphs.color_using_background_image("blue_yellow_gradient") + return graphs + def get_bottom_graphs(): + a = np.exp(a_mob.get_center()[0]) + graphs = VGroup(*[ + frequency_axes.get_graph(lambda t : np.exp(-(5./a)*(t-v)**2)) + for v in v_values + ]) + graphs.highlight(RED) + return graphs + + top_graphs = get_top_graphs() + bottom_graphs = get_bottom_graphs() + update_top_graphs = ContinualUpdateFromFunc( + top_graphs, + lambda g : Transform(g, get_top_graphs()).update(1) + ) + update_bottom_graphs = ContinualUpdateFromFunc( + bottom_graphs, + lambda g : Transform(g, get_bottom_graphs()).update(1) + ) + + self.add(time_axes, frequency_axes) + self.add(update_top_graphs, update_bottom_graphs) + + shift_vect = 2*RIGHT + for s in 1, -2, 1: + self.play(a_mob.shift, s*shift_vect, run_time = 3) + +class MentionUncertaintyPrincipleCopy(MentionUncertaintyPrinciple): + pass + +class IntroduceDeBroglie(Scene): + CONFIG = { + "default_wave_frequency" : 1, + "wave_colors" : [BLUE_D, YELLOW], + "dispersion_factor" : 1, + "amplitude" : 1, + } + def construct(self): + text_scale_val = 0.8, + + #Overlay real tower in video editor + eiffel_tower = Line(3*DOWN, 3*UP, stroke_width = 0) + picture = ImageMobject("de_Broglie") + picture.scale_to_fit_height(4) + picture.to_corner(UP+LEFT) + name = TextMobject("Louis de Broglie") + name.next_to(picture, DOWN) + + picture.save_state() + picture.scale(0) + picture.move_to(eiffel_tower.get_top()) + + + broadcasts = [ + Broadcast( + eiffel_tower.get_top(), + big_radius = 10, + n_circles = 10, + lag_ratio = 0.9, + run_time = 7, + rate_func = squish_rate_func(smooth, a, a+0.3), + color = WHITE, + ) + for a in np.linspace(0, 0.7, 3) + ] + + self.play(*broadcasts) + self.play(picture.restore) + self.play(Write(name)) + self.wait() + + #Time line + time_line = NumberLine( + x_min = 1900, + x_max = 1935, + tick_frequency = 1, + numbers_with_elongated_ticks = range(1900, 1941, 10), + color = BLUE_D + ) + time_line.stretch_to_fit_width(2*SPACE_WIDTH - picture.get_width() - 2) + time_line.add_numbers(*time_line.numbers_with_elongated_ticks) + time_line.next_to(picture, RIGHT, MED_LARGE_BUFF, DOWN) + + year_to_words = { + 1914 : "Wold War I begins", + 1915 : "Einstein field equations", + 1916 : "Lewis dot formulas", + 1917 : "Not a lot of physics...because war", + 1918 : "S'more Rutherford badassery", + 1919 : "Eddington confirms general relativity predictions", + 1920 : "World is generally stoked on general relativity", + 1921 : "Einstein gets long overdue Nobel prize", + 1922 : "Stern-Gerlach Experiment", + 1923 : "Compton scattering observed", + 1924 : "de Broglie's thesis" + } + arrow = Vector(DOWN, color = WHITE) + arrow.next_to(time_line.number_to_point(1914), UP) + words = TextMobject(year_to_words[1914]) + words.scale(text_scale_val) + date = Integer(1914) + date.next_to(arrow, UP, LARGE_BUFF) + + def get_year(alpha = 0): + return int(time_line.point_to_number(arrow.get_end())) + + def update_words(words): + text = year_to_words.get(get_year(), "Hi there") + if text not in words.get_tex_string(): + words.__init__(text) + words.scale(text_scale_val) + words.move_to(interpolate( + arrow.get_top(), date.get_bottom(), 0.5 + )) + update_words(words) + self.play( + FadeIn(time_line), + GrowArrow(arrow), + Write(words), + Write(date), + run_time = 1 + ) + self.wait() + self.play( + arrow.next_to, time_line.number_to_point(1924), UP, + ChangingDecimal( + date, get_year, + position_update_func = lambda m : m.next_to(arrow, UP, LARGE_BUFF) + ), + UpdateFromFunc(words, update_words), + run_time = 3, + ) + self.wait() + + #Transform time_line + line = time_line.main_line + self.play( + FadeOut(time_line.numbers), + VGroup(arrow, words, date).shift, MED_LARGE_BUFF*UP, + *[ + ApplyFunction( + lambda m : m.rotate(TAU/4).set_stroke(width = 0), + mob, + remover = True + ) + for mob in time_line.tick_marks + ] + ) + + #Wave function + particle = VectorizedPoint() + axes = Axes(x_min = -1, x_max = 10) + axes.match_width(line) + axes.shift(line.get_center() - axes.x_axis.get_center()) + im_line = line.copy() + im_line.highlight(YELLOW) + wave_update_animation = self.get_wave_update_animation( + axes, particle, line, im_line + ) + + for x in range(3): + particle.move_to(axes.coords_to_point(-10, 0)) + self.play( + ApplyMethod( + particle.move_to, axes.coords_to_point(22, 0), + rate_func = None + ), + wave_update_animation, + run_time = 3 + ) + self.wait() + + ### + def get_wave_update_animation(self, axes, particle, re_line = None, im_line = None): + line = Line( + axes.x_axis.get_left(), + axes.x_axis.get_right(), + ) + if re_line is None: + re_line = line.copy() + re_line.highlight(self.wave_colors[0]) + if im_line is None: + im_line = line.copy() + im_line.highlight(self.wave_colors[1]) + lines = VGroup(im_line, re_line) + def update_lines(lines): + waves = self.get_wave_pair(axes, particle) + for line, wave in zip(lines, waves): + wave.match_style(line) + Transform(line, wave).update(1) + return UpdateFromFunc(lines, update_lines) + + def get_wave( + self, axes, particle, + complex_to_real_func = lambda z : z.real, + freq = None, + **kwargs): + freq = freq or self.default_wave_frequency + k0 = 1./freq + t0 = axes.x_axis.point_to_number(particle.get_center()) + def func(x): + dispersion = fdiv(1., self.dispersion_factor)*(np.sqrt(1./(1+t0**2))) + wave_part = complex_to_real_func(np.exp( + complex(0, TAU*freq*(x-dispersion)) + )) + bell_part = np.exp(-dispersion*(x-t0)**2) + amplitude = self.amplitude + return amplitude*wave_part*bell_part + graph = axes.get_graph(func) + return graph + + def get_wave_pair(self, axes, particle, colors = None, **kwargs): + if colors is None and "color" not in kwargs: + colors = self.wave_colors + return VGroup(*[ + self.get_wave( + axes, particle, + C_to_R, color = color, + **kwargs + ) + for C_to_R, color in zip( + [lambda z : z.imag, lambda z : z.real], + colors + ) + ]) + +class ShowMomentumFormula(IntroduceDeBroglie, TeacherStudentsScene): + CONFIG = { + "default_wave_frequency" : 2, + "dispersion_factor" : 0.25, + "p_color" : BLUE, + "xi_color" : YELLOW, + "amplitude" : 0.5, + } + def construct(self): + self.introduce_formula() + self.react_to_claim() + + def introduce_formula(self): + formula = p, eq, h, xi = TexMobject("p", "=", "h", "\\xi") + formula.move_to(ORIGIN) + formula.scale(1.5) + + word_shift_val = 1.75 + p_words = TextMobject("Momentum") + p_words.next_to(p, UP, LARGE_BUFF).shift(word_shift_val*LEFT) + p_arrow = Arrow( + p_words.get_bottom(), p.get_corner(UP+LEFT), + buff = SMALL_BUFF + ) + added_p_words = TextMobject("(Classically $m \\times v$)") + added_p_words.move_to(p_words, DOWN) + VGroup(p, p_words, added_p_words, p_arrow).highlight(self.p_color) + + xi_words = TextMobject("Spatial frequency") + added_xi_words = TextMobject("(cycles per unit \\emph{distance})") + xi_words.next_to(xi, UP, LARGE_BUFF).shift(word_shift_val*RIGHT) + xi_words.align_to(p_words) + xi_arrow = Arrow( + xi_words.get_bottom(), xi.get_corner(UP+RIGHT), + buff = SMALL_BUFF + ) + added_xi_words.move_to(xi_words, DOWN) + added_xi_words.align_to(added_p_words, DOWN) + VGroup(xi, xi_words, added_xi_words, xi_arrow).highlight(self.xi_color) + + axes = Axes( + x_min = 0, x_max = 2*SPACE_WIDTH, + y_min = -1, y_max = 1, + ) + axes.center().to_edge(UP, buff = -0.5) + # axes.next_to(formula, RIGHT) + particle = VectorizedPoint() + wave_update_animation = self.get_wave_update_animation(axes, particle) + wave = wave_update_animation.mobject + wave[0].set_stroke(width = 0) + particle.next_to(wave, LEFT, buff = 2) + wave_propagation = AnimationGroup( + ApplyMethod(particle.move_to, axes.coords_to_point(30, 0)), + wave_update_animation, + run_time = 4, + rate_func = None, + ) + stopped_wave_propagation = AnimationGroup( + ApplyMethod(particle.move_to, xi_words), + wave_update_animation, + run_time = 3, + rate_func = None, + ) + n_v_lines = 10 + v_lines = VGroup(*[ + DashedLine(UP, DOWN) + for x in range(n_v_lines) + ]) + v_lines.match_color(xi) + v_lines.arrange_submobjects( + RIGHT, + buff = float(axes.x_axis.unit_size)/self.default_wave_frequency + ) + v_lines.move_to(stopped_wave_propagation.sub_anims[0].target_mobject) + v_lines.align_to(wave) + v_lines.shift(0.125*RIGHT) + + self.add(formula, wave) + self.play( + self.teacher.change, "raise_right_hand", + GrowArrow(p_arrow), + Succession( + Write, p_words, + ApplyMethod, p_words.next_to, added_p_words, UP, + ), + FadeIn( + added_p_words, + rate_func = squish_rate_func(smooth, 0.5, 1), + run_time = 2, + ), + wave_propagation + ) + self.play( + Write(xi_words), + GrowArrow(xi_arrow), + self.get_student_changes("confused", "erm", "sassy"), + stopped_wave_propagation + ) + self.play( + FadeIn(added_xi_words), + xi_words.next_to, added_xi_words, UP, + ) + self.play( + LaggedStart(ShowCreation, v_lines), + self.get_student_changes(*["pondering"]*3) + ) + self.play(LaggedStart(FadeOut, v_lines)) + self.wait() + + self.formula_labels = VGroup( + p_words, p_arrow, added_p_words, + xi_words, xi_arrow, added_xi_words, + ) + self.set_variables_as_attrs(wave, wave_propagation, formula) + + def react_to_claim(self): + formula_labels = self.formula_labels + full_formula = VGroup(self.formula, formula_labels) + full_formula.save_state() + wave_propagation = self.wave_propagation + + student = self.students[2] + self.student_says( + "Hang on...", + bubble_kwargs = {"height" : 2, "width" : 2, "direction" : LEFT}, + target_mode = "sassy", + student_index = 2, + added_anims = [self.teacher.change, "plain"] + ) + student.bubble.add(student.bubble.content) + self.wait() + kwargs = { + "path_arc" : TAU/4, + "submobject_mode" : "lagged_start", + "lag_ratio" : 0.7, + "run_time" : 1.5, + } + self.play( + full_formula.scale, 0, + full_formula.move_to, student.eyes.get_bottom()+SMALL_BUFF*DOWN, + Animation(student.bubble), + **kwargs + ) + self.play(full_formula.restore, Animation(student.bubble), **kwargs) + wave_propagation.update_config( + rate_func = lambda a : interpolate(0.35, 1, a) + ) + self.play( + wave_propagation, + RemovePiCreatureBubble(student, target_mode = "confused"), + ) + wave_propagation.update_config(rate_func = lambda t : t) + self.student_says( + "Physics is \\\\ just weird", + bubble_kwargs = {"height" : 2.5, "width" : 3}, + target_mode = "shruggie", + student_index = 0, + added_anims = [ApplyMethod(full_formula.shift, UP)] + ) + self.wait() + self.play( + wave_propagation, + ApplyMethod(full_formula.shift, DOWN), + FadeOut(self.students[0].bubble), + FadeOut(self.students[0].bubble.content), + self.get_student_changes(*3*["pondering"]), + self.teacher.change, "pondering", + ) + self.play(wave_propagation) + +class AskPhysicists(PiCreatureScene): + def construct(self): + morty, physy1, physy2, physy3 = self.pi_creatures + formula = TexMobject("p", "=", "h", "\\xi") + formula.highlight_by_tex_to_color_map({ + "p" : BLUE, + "\\xi" : YELLOW, + }) + formula.scale(1.5) + + formula.to_edge(UP) + formula.save_state() + formula.shift(DOWN) + formula.fade(1) + self.play(formula.restore) + self.pi_creature_says( + morty, "So...why?", + target_mode = "maybe" + ) + self.wait(2) + self.play( + RemovePiCreatureBubble(morty), + PiCreatureSays( + physy2, + "Take the Schrödinger equation \\\\ with $H = \\frac{p^2}{2m}+V(x)$", + bubble_kwargs = {"fill_opacity" : 0.9}, + ), + ) + self.play( + PiCreatureSays( + physy1, + "Even classically position and \\\\ momentum are conjugate", + target_mode = "surprised", + bubble_kwargs = {"fill_opacity" : 0.9}, + ), + ) + self.play( + PiCreatureSays( + physy3, + "Consider special relativity \\\\ together with $E = hf$", + target_mode = "hooray", + bubble_kwargs = {"fill_opacity" : 0.9}, + ), + morty.change, "guilty" + ) + self.wait(2) + + + + ### + + def create_pi_creatures(self): + scale_factor = 0.85 + morty = Mortimer().flip() + morty.scale(scale_factor) + morty.to_corner(DOWN+LEFT) + + physies = VGroup(*[ + PiCreature(color = c).flip() + for c in GREY, LIGHT_GREY, DARK_GREY + ]) + physies.arrange_submobjects(RIGHT, buff = MED_SMALL_BUFF) + physies.scale(scale_factor) + physies.to_corner(DOWN+RIGHT) + + self.add(physies) + return VGroup(morty, *physies) + +class SortOfDopplerEffect(PiCreatureScene): + CONFIG = { + "omega" : np.pi, + "arrow_spacing" : 0.25, + } + def setup(self): + PiCreatureScene.setup(self) + rect = self.screen_rect = ScreenRectangle(height = 2*SPACE_HEIGHT) + rect.set_stroke(width = 0) + self.camera = MovingCamera( + rect, **self.camera_config + ) + + def construct(self): + screen_rect = self.screen_rect + + #x-coordinate gives time + t_tracker = VectorizedPoint() + #x-coordinate gives wave number + k_tracker = VectorizedPoint(2*RIGHT) + tk_movement = AmbientMovement(t_tracker, direction = RIGHT, rate = 1) + def get_wave(): + t = t_tracker.get_center()[0] + k = k_tracker.get_center()[0] + omega = self.omega + color = interpolate_color( + BLUE, RED, (k-2)/2.0 + ) + func = lambda x : 0.5*np.cos(omega*t - k*x) + graph = FunctionGraph( + func, + x_min = -5*SPACE_WIDTH, + x_max = SPACE_WIDTH, + color = color, + ) + return VGroup(graph, *[ + Arrow( + x*RIGHT, x*RIGHT + func(x)*UP, + color = color + ) + for x in np.arange( + -4*SPACE_WIDTH, SPACE_WIDTH, + self.arrow_spacing + ) + ]) + return + wave = get_wave() + wave_update = ContinualUpdateFromFunc( + wave, lambda w : Transform(w, get_wave()).update(1) + ) + + rect = ScreenRectangle(height = 2) + rect.to_edge(RIGHT) + rect_movement = AmbientMovement(rect, direction = LEFT, rate = 1) + + randy = self.pi_creature + randy_look_at = ContinualUpdateFromFunc( + randy, lambda r : r.look_at(rect) + ) + + ref_frame1 = TextMobject("Reference frame 1") + # ref_frame1.next_to(randy, UP, aligned_edge = LEFT) + ref_frame1.to_edge(UP) + ref_frame2 = TextMobject("Reference frame 2") + ref_frame2.next_to(rect, UP) + # ref_frame2.set_fill(opacity = 0) + ref_frame2_follow = ContinualUpdateFromFunc( + ref_frame2, lambda m : m.next_to(rect, UP) + ) + ref_frame_1_continual_anim = ContinualAnimation(ref_frame1) + + self.add( + tk_movement, wave_update, rect_movement, randy_look_at, + ref_frame2_follow, ref_frame_1_continual_anim + ) + self.add(ref_frame1) + self.play(randy.change, "pondering") + self.wait(4) + start_height = screen_rect.get_height() + start_center = screen_rect.get_center() + self.play( + UpdateFromAlphaFunc( + screen_rect, + lambda m, a : m.move_to( + interpolate(start_center, rect.get_center(), a) + ) + ), + k_tracker.shift, 2*RIGHT, + ) + self.play( + MaintainPositionRelativeTo( + screen_rect, rect, + run_time = 4 + ), + ) + self.play( + screen_rect.move_to, rect.get_right()+SPACE_WIDTH*LEFT, + k_tracker.shift, 2*LEFT, + ) + + #Frequency words + temporal_frequency = TextMobject("Temporal", "frequency") + spatial_frequency = TextMobject("Spatial", "frequency") + temporal_frequency.move_to(screen_rect).to_edge(UP) + spatial_frequency.next_to(temporal_frequency, DOWN) + cross = Cross(temporal_frequency[0]) + + time = TextMobject("Time") + space = TextMobject("Space") + time.next_to(temporal_frequency, RIGHT, buff = 2) + space.next_to(time, DOWN) + space.align_to(spatial_frequency) + + self.play(FadeIn(temporal_frequency)) + self.play(ShowCreation(cross)) + self.play(Write(spatial_frequency)) + self.wait() + self.play(FadeIn(time), FadeIn(space)) + self.play( + Transform(time, space), + Transform(space, time), + submobject_mode = "lagged_start", + run_time = 1, + ) + self.play(FadeOut(time), FadeOut(space)) + self.wait(3) + + ### + + def create_pi_creature(self): + return Randolph().scale(0.5).to_corner(DOWN+LEFT) + +class HangingWeightsScene(MovingCameraScene): + CONFIG = { + "frequency" : 0.5, + "ceiling_radius" : 3*SPACE_WIDTH, + "n_springs" : 72, + "amplitude" : 0.6, + "spring_radius" : 0.15, + } + def construct(self): + self.setup_springs() + self.setup_weights() + self.introduce() + self.show_analogy_with_electron() + self.metaphor_for_something() + self.moving_reference_frame() + + def setup_springs(self): + ceiling = self.ceiling = Line(LEFT, RIGHT) + ceiling.scale(self.ceiling_radius) + ceiling.to_edge(UP, buff = LARGE_BUFF) + self.add(ceiling) + + def get_spring(alpha, height = 2): + t_max = 6.5 + r = self.spring_radius + s = (height - r)/(t_max**2) + spring = ParametricFunction( + lambda t : op.add( + r*(np.sin(TAU*t)*RIGHT+np.cos(TAU*t)*UP), + s*((t_max - t)**2)*DOWN, + ), + t_min = 0, t_max = t_max, + color = WHITE, + stroke_width = 2, + ) + spring.alpha = alpha + spring.move_to(ceiling.point_from_proportion(alpha), UP) + spring.color_using_background_image("grey_gradient") + return spring + alphas = np.linspace(0, 1, self.n_springs) + bezier([0, 1, 0, 1]) + springs = self.springs = VGroup(*map(get_spring, alphas)) + + k_tracker = self.k_tracker = VectorizedPoint() + t_tracker = self.t_tracker = VectorizedPoint() + self.t_tracker_walk = AmbientMovement(t_tracker, direction = RIGHT, rate = 1) + equilibrium_height = springs.get_height() + def update_springs(springs): + for spring in springs: + k = k_tracker.get_center()[0] + t = t_tracker.get_center()[0] + f = self.frequency + x = spring.get_top()[0] + A = self.amplitude + d_height = A*np.cos(TAU*f*t - k*x) + new_spring = get_spring(spring.alpha, 2+d_height) + Transform(spring, new_spring).update(1) + spring_update_anim = ContinualUpdateFromFunc(springs, update_springs) + self.spring_update_anim = spring_update_anim + spring_update_anim.update(0) + + self.play( + ShowCreation(ceiling), + LaggedStart(ShowCreation, springs) + ) + + def setup_weights(self): + weights = self.weights = VGroup() + weight_anims = weight_anims = [] + for spring in self.springs: + x = spring.get_top()[0] + mass = np.exp(-0.1*x**2) + weight = Circle(radius = 0.15) + weight.start_radius = 0.15 + weight.target_radius = 0.25*mass #For future update + weight.spring = spring + weight_anim = ContinualUpdateFromFunc( + weight, lambda w : w.move_to(w.spring.get_bottom()) + ) + weight_anim.update(0) + weight_anims.append(weight_anim) + weights.add(weight) + weights.set_fill(opacity = 1) + weights.gradient_highlight(BLUE_D, BLUE_E, BLUE_D) + weights.set_stroke(WHITE, 1) + + self.play(LaggedStart(GrowFromCenter, weights)) + self.add(self.t_tracker_walk) + self.add(self.spring_update_anim) + self.add(*weight_anims) + + def introduce(self): + arrow = Arrow(4*LEFT, LEFT) + arrows = VGroup(arrow, arrow.copy().flip(about_point = ORIGIN)) + arrows.highlight(WHITE) + + self.wait(3) + self.play(*map(GrowArrow, arrows)) + self.play(*[ + UpdateFromAlphaFunc( + weight, lambda w, a : w.scale_to_fit_width( + 2*interpolate(w.start_radius, w.target_radius, a) + ), + run_time = 2 + ) + for weight in self.weights + ]) + self.play(FadeOut(arrows)) + self.wait(3) + + def show_analogy_with_electron(self): + words = TextMobject( + "Analogous to the energy of a particle \\\\", + "(in the sense of $E=mc^2$)" + ) + words.move_to(DOWN) + + self.play(Write(words)) + self.wait(3) + self.play(FadeOut(words)) + + def metaphor_for_something(self): + de_broglie = ImageMobject("de_Broglie") + de_broglie.scale_to_fit_height(3.5) + de_broglie.to_corner(DOWN+RIGHT) + words = TextMobject(""" + If a photon's energy is carried as a wave \\\\ + is this true for any particle? + """) + words.next_to(de_broglie, LEFT) + + einstein = ImageMobject("Einstein") + einstein.match_height(de_broglie) + einstein.to_corner(DOWN+LEFT) + + for picture in de_broglie, einstein: + picture.backdrop = Rectangle() + picture.backdrop.replace(picture, stretch = True) + picture.backdrop.set_fill(BLACK, 1) + picture.backdrop.set_stroke(BLACK, 0) + + self.play( + Animation(de_broglie.backdrop, remover = True), + FadeIn(de_broglie) + ) + self.play(Write(words)) + self.wait(7) + self.play( + FadeOut(words), + Animation(einstein.backdrop, remover = True), + FadeIn(einstein) + ) + self.wait(2) + + self.de_broglie = de_broglie + self.einstein = einstein + + def moving_reference_frame(self): + rect = ScreenRectangle(height = 2.1*SPACE_HEIGHT) + rect_movement = AmbientMovement(rect, direction = LEFT, rate = 2) + camera_frame = self.camera_frame + + self.add(rect) + self.play( + Animation(self.de_broglie.backdrop, remover = True), + FadeOut(self.de_broglie), + Animation(self.einstein.backdrop, remover = True), + FadeOut(self.einstein), + ) + self.play(camera_frame.scale, 3, {"about_point" : 2*UP}) + self.play(rect.shift, 2*SPACE_WIDTH*RIGHT, path_arc = -TAU/2) + self.add(rect_movement) + self.wait(3) + + def zoom_into_reference_frame(): + original_height = camera_frame.get_height() + original_center = camera_frame.get_center() + self.play( + UpdateFromAlphaFunc( + camera_frame, lambda c, a : c.scale_to_fit_height( + interpolate(original_height, 0.95*rect.get_height(), a) + ).move_to( + interpolate(original_center, rect.get_center(), a) + ) + ), + ApplyMethod(self.k_tracker.shift, RIGHT) + ) + self.play(MaintainPositionRelativeTo( + camera_frame, rect, + run_time = 6 + )) + self.play( + camera_frame.scale_to_fit_height, original_height, + camera_frame.move_to, original_center, + ApplyMethod(self.k_tracker.shift, LEFT) + ) + + zoom_into_reference_frame() + self.wait() + self.play( + UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*(1-a))) + ) + + index = int(0.5*len(self.springs)) + weights = VGroup(self.weights[index], self.weights[index+4]) + flashes = map(self.get_peak_flash_anim, weights) + weights.save_state() + weights.set_fill(RED) + self.add(*flashes) + self.wait(5) + + rect.align_to(camera_frame, RIGHT) + self.play(UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*a))) + + randy = Randolph(mode = "pondering") + randy.look(UP+RIGHT) + de_broglie = ImageMobject("de_Broglie") + de_broglie.scale_to_fit_height(6) + de_broglie.next_to(4*DOWN, DOWN) + self.add( + ContinualUpdateFromFunc( + randy, lambda m : m.next_to( + rect.get_corner(DOWN+LEFT), UP+RIGHT, MED_LARGE_BUFF, + ).look_at(weights) + ), + de_broglie + ) + self.wait(2) + + zoom_into_reference_frame() + self.wait(8) + + ### + + def get_peak_flash_anim(self, weight): + mobject = Mobject() #Dummy + mobject.last_y = 0 + mobject.last_dy = 0 + mobject.curr_anim = None + mobject.curr_anim_time = 0 + mobject.time_since_last_flash = 0 + def update(mob, dt): + mob.time_since_last_flash += dt + point = weight.get_center() + y = point[1] + mob.dy = y - mob.last_y + different_dy = np.sign(mob.dy) != np.sign(mob.last_dy) + if different_dy and mob.time_since_last_flash > 0.5: + mob.curr_anim = Flash( + VectorizedPoint(point), + flash_radius = 0.5, + line_length = 0.3, + run_time = 0.2, + ) + mob.submobjects = [mob.curr_anim.mobject] + mob.time_since_last_flash = 0 + mob.last_y = float(y) + mob.last_dy = float(mob.dy) + ## + if mob.curr_anim: + mob.curr_anim_time += dt + if mob.curr_anim_time > mob.curr_anim.run_time: + mob.curr_anim = None + mob.submobjects = [] + mob.curr_anim_time = 0 + return + mob.curr_anim.update(mob.curr_anim_time/mob.curr_anim.run_time) + + return ContinualUpdateFromTimeFunc(mobject, update) + +class MinutPhysicsWrapper(Scene): + def construct(self): + logo = ImageMobject("minute_physics_logo", invert = True) + logo.to_corner(UP+LEFT) + self.add(logo) + + title = TextMobject("Minute Physics on special relativity") + title.to_edge(UP).shift(MED_LARGE_BUFF*RIGHT) + + screen_rect = ScreenRectangle() + screen_rect.scale_to_fit_width(title.get_width() + LARGE_BUFF) + screen_rect.next_to(title, DOWN) + + self.play(ShowCreation(screen_rect)) + self.play(Write(title)) + self.wait(2) + +class WhatDoesTheFourierTradeoffTellUs(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "So! What does \\\\ the Fourier trade-off \\\\ tell us?", + target_mode = "surprised", + bubble_kwargs = {"width" : 4, "height" : 3} + ) + self.change_student_modes(*["thinking"]*3) + self.wait(4) + +class FourierTransformOfWaveFunction(Scene): + CONFIG = { + "wave_stroke_width" : 3, + "wave_color" : BLUE, + } + def construct(self): + self.show_wave_packet() + self.take_fourier_transform() + self.show_correlations_with_pure_frequencies() + self.this_is_momentum() + self.show_tradeoff() + + def setup(self): + self.x0_tracker = ValueTracker(-3) + self.k_tracker = ValueTracker(1) + self.a_tracker = ExponentialValueTracker(0.5) + + def show_wave_packet(self): + axes = Axes( + x_min = 0, x_max = 12, + y_min = -1, y_max = 1, + y_axis_config = { + "tick_frequency" : 0.5 + } + ) + position_label = TextMobject("Position") + position_label.next_to(axes.x_axis.get_right(), UP) + axes.add(position_label) + axes.center().to_edge(UP, buff = LARGE_BUFF) + + wave = self.get_wave(axes) + wave_update_animation = UpdateFromFunc( + wave, lambda w : Transform(w, self.get_wave(axes)).update(1) + ) + + self.add(axes, wave) + self.play( + self.x0_tracker.set_value, 5, + wave_update_animation, + run_time = 3, + ) + self.wait() + + self.wave_function = wave.underlying_function + self.wave_update_animation = wave_update_animation + self.wave = wave + self.axes = axes + + def take_fourier_transform(self): + wave = self.wave + wave_update_animation = self.wave_update_animation + frequency_axes = Axes( + x_min = 0, x_max = 3, + x_axis_config = { + "unit_size" : 4, + "tick_frequency" : 0.25, + "numbers_with_elongated_ticks" : [1, 2] + }, + y_min = -0.15, + y_max = 0.15, + y_axis_config = { + "unit_size" : 7.5, + "tick_frequency" : 0.05, + } + ) + label = self.frequency_x_axis_label = TextMobject("Spatial frequency") + label.next_to(frequency_axes.x_axis.get_right(), UP) + frequency_axes.add(label) + frequency_axes.move_to(self.axes, LEFT) + frequency_axes.to_edge(DOWN, buff = LARGE_BUFF) + label.shift_onto_screen() + + def get_wave_function_fourier_graph(): + return get_fourier_graph( + frequency_axes, self.get_wave_func(), + t_min = 0, t_max = 15, + ) + fourier_graph = get_wave_function_fourier_graph() + self.fourier_graph_update_animation = UpdateFromFunc( + fourier_graph, lambda m : Transform( + m, get_wave_function_fourier_graph() + ).update(1) + ) + + wave_copy = wave.copy() + wave_copy.generate_target() + wave_copy.target.move_to(fourier_graph, LEFT) + wave_copy.target.fade(1) + fourier_graph.save_state() + fourier_graph.move_to(wave, LEFT) + fourier_graph.fade(1) + + arrow = Arrow( + self.axes.coords_to_point(5, -1), + frequency_axes.coords_to_point(1, 0.1), + color = YELLOW, + ) + fourier_label = TextMobject("Fourier Transform") + fourier_label.next_to(arrow.get_center(), RIGHT) + + self.play(ReplacementTransform( + self.axes.copy(), frequency_axes + )) + self.play( + MoveToTarget(wave_copy, remover = True), + fourier_graph.restore, + GrowArrow(arrow), + Write(fourier_label, run_time = 1), + ) + self.wait() + + self.frequency_axes = frequency_axes + self.fourier_graph = fourier_graph + self.fourier_label = VGroup(arrow, fourier_label) + + def show_correlations_with_pure_frequencies(self): + frequency_axes = self.frequency_axes + axes = self.axes + + sinusoid = axes.get_graph( + lambda x : 0.5*np.cos(TAU*x), + x_min = -SPACE_WIDTH, x_max = 3*SPACE_WIDTH, + ) + sinusoid.to_edge(UP, buff = SMALL_BUFF) + + v_line = DashedLine(1.5*UP, ORIGIN, color = YELLOW) + v_line.move_to(frequency_axes.coords_to_point(1, 0), DOWN) + + f_equals = TexMobject("f = ") + freq_decimal = DecimalNumber(1) + freq_decimal.next_to(f_equals, RIGHT, buff = SMALL_BUFF) + freq_label = VGroup(f_equals, freq_decimal) + freq_label.next_to( + v_line, UP, SMALL_BUFF, + submobject_to_align = f_equals[0] + ) + + self.play( + ShowCreation(sinusoid), + ShowCreation(v_line), + Write(freq_label, run_time = 1), + FadeOut(self.fourier_label) + ) + last_f = 1 + for f in 1.4, 0.7, 1: + self.play( + sinusoid.stretch,f/last_f, 0, + {"about_point" : axes.coords_to_point(0, 0)}, + v_line.move_to, frequency_axes.coords_to_point(f, 0), DOWN, + MaintainPositionRelativeTo(freq_label, v_line), + ChangeDecimalToValue(freq_decimal, f), + run_time = 3, + ) + last_f = f + self.play(*map(FadeOut, [ + sinusoid, v_line, freq_label + ])) + + def this_is_momentum(self): + formula = TexMobject("p", "=", "h", "\\xi") + formula.highlight_by_tex_to_color_map({ + "p" : BLUE, + "xi" : YELLOW, + }) + formula.next_to( + self.frequency_x_axis_label, UP + ) + + f_max = 0.12 + brace = Brace(Line(2*LEFT, 2*RIGHT), UP) + brace.move_to(self.frequency_axes.coords_to_point(1, f_max), DOWN) + words = TextMobject("This wave \\\\ describes momentum") + words.next_to(brace, UP) + + self.play(Write(formula)) + self.wait() + self.play( + GrowFromCenter(brace), + Write(words) + ) + brace.add(words) + for k in 2, 0.5, 1: + self.play( + self.k_tracker.set_value, k, + self.wave_update_animation, + self.fourier_graph_update_animation, + UpdateFromFunc( + brace, lambda b : b.move_to( + self.frequency_axes.coords_to_point( + self.k_tracker.get_value(), + f_max, + ), + DOWN + ) + ), + run_time = 2 + ) + self.wait() + self.play(*map(FadeOut, [brace, words, formula])) + + def show_tradeoff(self): + for a in 5, 0.1, 0.01, 10, 0.5: + self.play( + ApplyMethod( + self.a_tracker.set_value, a, + run_time = 2 + ), + self.wave_update_animation, + self.fourier_graph_update_animation + ) + self.wait() + + ## + + def get_wave_func(self): + x0 = self.x0_tracker.get_value() + k = self.k_tracker.get_value() + a = self.a_tracker.get_value() + A = a**(0.25) + return lambda x : A*np.cos(TAU*k*x)*np.exp(-a*(x - x0)**2) + + def get_wave(self, axes): + return axes.get_graph( + self.get_wave_func(), + color = self.wave_color, + stroke_width = self.wave_stroke_width + ) + +class DopplerComparisonTodos(TODOStub): + CONFIG = { + "message" : """ + Insert some Doppler footage, + insert some hanging spring scene, + insert position-momentum Fourier trade-off + """ + } + +class MusicalNote(AddingPureFrequencies): + def construct(self): + speaker = self.speaker = SVGMobject(file_name = "speaker") + speaker.move_to(2*DOWN) + randy = self.pi_creature + + axes = Axes( + x_min = 0, x_max = 10, + y_min = -1.5, y_max = 1.5 + ) + axes.center().to_edge(UP) + time_label = TextMobject("Time") + time_label.next_to(axes.x_axis.get_right(), UP) + axes.add(time_label) + + graph = axes.get_graph( + lambda x : op.mul( + np.exp(-0.2*(x-4)**2), + 0.3*(np.cos(2*TAU*x) + np.cos(3*TAU*x) + np.cos(5*TAU*x)), + ), + ) + graph.highlight(BLUE) + v_line = DashedLine(ORIGIN, 0.5*UP) + v_line_update = UpdateFromFunc( + v_line, lambda l : l.put_start_and_end_on_with_projection( + graph.points[-1], + axes.x_axis.number_to_point( + axes.x_axis.point_to_number(graph.points[-1]) + ) + ) + ) + + self.add(speaker, axes) + self.play( + randy.change, "pondering", + self.get_broadcast_animation(n_circles = 6, run_time = 5), + self.get_broadcast_animation(n_circles = 12, run_time = 5), + ShowCreation(graph, run_time = 5, rate_func = None), + v_line_update + ) + self.wait(2) + +class AskAboutUncertainty(TeacherStudentsScene): + def construct(self): + self.student_says( + "What does this have \\\\ to do with ``certainty''", + bubble_kwargs = {"direction" : LEFT}, + student_index = 2 + ) + self.play(PiCreatureSays( + self.students[0], + "What even are \\\\ these waves?", + target_mode = "confused" + )) + self.wait(2) + +class ProbabalisticDetection(FourierTransformOfWaveFunction): + CONFIG = { + "wave_stroke_width" : 2, + } + def construct(self): + self.setup_wave() + self.detect_only_single_points() + self.show_probability_distribution() + self.show_concentration_of_the_wave() + + def setup_wave(self): + axes = Axes( + x_min = 0, x_max = 10, + y_min = -0.5, y_max = 1.5, + y_axis_config = { + "unit_size" : 1.5, + "tick_frequency" : 0.25, + } + ) + axes.set_stroke(width = 2) + axes.center() + self.x0_tracker.set_value(5) + self.k_tracker.set_value(1) + self.a_tracker.set_value(0.2) + wave = self.get_wave(axes) + self.wave_update_animation = UpdateFromFunc( + wave, lambda w : Transform(w, self.get_wave(axes)).update(1) + ) + + self.k_tracker.save_state() + self.k_tracker.set_value(0) + bell_curve = self.get_wave(axes) + self.k_tracker.restore() + bell_curve.set_stroke(width = 0) + bell_curve.set_fill(BLUE, opacity = 0.5) + squared_bell_curve = axes.get_graph( + lambda x : bell_curve.underlying_function(x)**2 + ).match_style(bell_curve) + + self.set_variables_as_attrs( + axes, wave, bell_curve, squared_bell_curve + ) + + def detect_only_single_points(self): + particle = ProbabalisticDotCloud( + n_copies = 100, + fill_opacity = 0.05, + time_per_change = 0.05, + ) + particle.mobject[0].set_fill(BLUE, opacity = 1) + gdw = particle.gaussian_distribution_wrapper + + rect = Rectangle( + stroke_width = 0, + height = 0.5, + width = 2, + ) + rect.set_fill(YELLOW, 0.3) + rect.move_to(self.axes.coords_to_point(self.x0_tracker.get_value(), 0)) + brace = Brace(rect, UP, buff = 0) + question = TextMobject("Do we detect the particle \\\\ in this region?") + question.next_to(brace, UP) + question.add_background_rectangle() + rect.save_state() + rect.stretch(0, 0) + + gdw_anim = ContinualUpdateFromFunc( + gdw, lambda m : m.scale_to_fit_width( + 2.0/(self.a_tracker.get_value()**(0.5)) + ).move_to(rect) + ) + + self.add(rect, brace, question) + + yes = TextMobject("Yes").highlight(GREEN) + no = TextMobject("No").highlight(RED) + for word in yes, no: + word.next_to(rect, DOWN) + # word.add_background_rectangle() + answer = VGroup() + def update_answer(answer): + px = particle.mobject[0].get_center()[0] + lx = rect.get_left()[0] + rx = rect.get_right()[0] + if lx < px < rx: + answer.submobjects = [yes] + else: + answer.submobjects = [no] + answer_anim = ContinualUpdateFromFunc(answer, update_answer) + + self.add(gdw_anim, particle) + self.play( + GrowFromCenter(brace), + rect.restore, + Write(question) + ) + self.wait() + self.add(answer_anim) + self.wait(4) + self.add_foreground_mobjects(answer, particle.mobject) + + self.question_group = VGroup(question, brace) + self.particle = particle + self.rect = rect + + def show_probability_distribution(self): + axes = self.axes + wave = self.wave + bell_curve = self.bell_curve + question_group = self.question_group + gdw = self.particle.gaussian_distribution_wrapper + rect = self.rect + + v_lines = VGroup(*[ + DashedLine(ORIGIN, 3*UP).move_to(point, DOWN) + for point in rect.get_left(), rect.get_right() + ]) + + self.play( + FadeIn(VGroup(axes, wave)), + question_group.next_to, v_lines, UP, {"buff" : 0}, + *map(ShowCreation, v_lines) + ) + self.wait(10) + + def show_concentration_of_the_wave(self): + self.play( + self.a_tracker.set_value, 5, + self.wave_update_animation, + ) + self.wait(10) + +class HeisenbergCommentTodos(TODOStub): + CONFIG = { + "message" : "Insert position-momentum trade-off" + } + +class HeisenbergPetPeeve(PiCreatureScene): + def construct(self): + morty, other = self.pi_creatures + particle = ProbabalisticDotCloud() + gdw = particle.gaussian_distribution_wrapper + gdw.to_edge(UP, buff = LARGE_BUFF) + gdw.stretch_to_fit_width(3) + gdw.rotate(3*DEGREES) + + self.add(particle) + self.wait() + self.play(PiCreatureSays( + other, """ + According to the H.U.P., the \\\\ + universe is unknowable! + """, + target_mode = "speaking" + )) + self.play(morty.change, "angry") + self.wait(3) + self.play( + PiCreatureSays( + morty, "Well, yes and no", + target_mode = "sassy", + ), + RemovePiCreatureBubble( + other, target_mode = "erm" + ) + ) + self.wait(4) + + ### + def create_pi_creatures(self): + morty = Mortimer() + morty.to_corner(DOWN+RIGHT) + other = PiCreature(color = MAROON_E) + other.to_edge(DOWN).shift(3*LEFT) + return VGroup(morty, other) + +class OneLevelDeeper(Scene): + def construct(self): + heisenberg = ImageMobject("Heisenberg") + heisenberg.to_corner(UP+LEFT) + self.add(heisenberg) + + hup_words = TextMobject("Heisenberg's uncertainty principle") + wave_words = TextMobject("Interpretation of the wave function") + arrow = Vector(UP) + group = VGroup(hup_words, arrow, wave_words) + group.arrange_submobjects(DOWN) + + randomness = ProbabalisticMobjectCloud( + TextMobject("Randomness"), + n_copies = 5, + time_per_change = 0.05 + ) + gdw = randomness.gaussian_distribution_wrapper + gdw.rotate(TAU/4) + gdw.scale_to_fit_height(1) + # gdw.scale_to_fit_width(4) + gdw.next_to(hup_words, UP, MED_LARGE_BUFF) + + self.add(hup_words, randomness) + self.wait(4) + self.play( + FadeIn(wave_words), + GrowArrow(arrow), + ApplyMethod( + gdw.next_to, wave_words, DOWN, MED_LARGE_BUFF, + path_arc = TAU/2, + ) + ) + self.wait(6) + +class BetterTranslation(TeacherStudentsScene): + def construct(self): + english_term = TextMobject("Uncertainty principle") + german_word = TextMobject("Unschärferelation") + translation = TextMobject("Unsharpness relation") + + to_german_words = TextMobject("In German") + to_german_words.scale(0.5) + to_german_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF) + to_german_words.next_to(to_german_arrow, RIGHT, SMALL_BUFF) + to_german_words.highlight(YELLOW) + to_german_group = VGroup(to_german_arrow, to_german_words) + + translation_words = TextMobject("Literal translation") + translation_words.scale(0.5) + translation_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF) + translation_words.next_to(translation_arrow, LEFT, SMALL_BUFF) + translation_words.highlight(YELLOW) + translation_group = VGroup(translation_arrow, translation_words) + + english_term.next_to(self.teacher, UP+LEFT) + english_term.save_state() + english_term.shift(DOWN) + english_term.fade(1) + self.play( + english_term.restore, + self.get_student_changes(*["pondering"]*3) + ) + self.wait() + + german_word.move_to(english_term) + to_german_group.next_to( + german_word, UP, + submobject_to_align = to_german_arrow + ) + self.play( + self.teacher.change, "raise_right_hand", + english_term.next_to, to_german_arrow, UP + ) + self.play( + GrowArrow(to_german_arrow), + FadeIn(to_german_words), + ReplacementTransform( + english_term.copy().fade(1), + german_word + ) + ) + self.wait(2) + + group = VGroup(english_term, to_german_group, german_word) + translation.move_to(german_word) + translation_group.next_to( + german_word, UP, + submobject_to_align = translation_arrow + ) + self.play( + group.next_to, translation_arrow, UP, + ) + self.play( + GrowArrow(translation_arrow), + FadeIn(translation_words), + ReplacementTransform( + german_word.copy().fade(1), + translation + ) + ) + self.change_student_modes(*["happy"]*3) + self.wait(2) + +class ThinkOfHeisenbergUncertainty(PiCreatureScene): + def construct(self): + morty = self.pi_creature + morty.center().to_edge(DOWN).shift(LEFT) + + dot_cloud = ProbabalisticDotCloud() + dot_gdw = dot_cloud.gaussian_distribution_wrapper + dot_gdw.scale_to_fit_width(1) + dot_gdw.rotate(TAU/8) + dot_gdw.move_to(SPACE_WIDTH*RIGHT/2), + + vector_cloud = ProbabalisticVectorCloud( + center_func = dot_gdw.get_center + ) + vector_gdw = vector_cloud.gaussian_distribution_wrapper + vector_gdw.scale_to_fit_width(0.1) + vector_gdw.rotate(TAU/8) + vector_gdw.next_to(dot_gdw, UP+LEFT, LARGE_BUFF) + + time_tracker = ValueTracker(0) + self.add() + freq = 1 + continual_anims = [ + AmbientMovement(time_tracker, direction = RIGHT, rate = 1), + ContinualUpdateFromFunc( + dot_gdw, + lambda d : d.scale_to_fit_width( + (np.cos(freq*time_tracker.get_value()) + 1.1)/2 + ) + ), + ContinualUpdateFromFunc( + vector_gdw, + lambda d : d.scale_to_fit_width( + (-np.cos(freq*time_tracker.get_value()) + 1.1)/2 + ) + ), + dot_cloud, vector_cloud + ] + self.add(*continual_anims) + + position, momentum, time, frequency = map(TextMobject, [ + "Position", "Momentum", "Time", "Frequency" + ]) + VGroup(position, time).highlight(BLUE) + VGroup(momentum, frequency).highlight(YELLOW) + groups = VGroup() + for m1, m2 in (position, momentum), (time, frequency): + arrow = TexMobject("\\updownarrow").scale(1.5) + group = VGroup(m1, arrow, m2) + group.arrange_submobjects(DOWN) + lp, rp = parens = TexMobject("\\big(\\big)") + parens.stretch(1.5, 1) + parens.match_height(group) + lp.next_to(group, LEFT, buff = SMALL_BUFF) + rp.next_to(group, RIGHT, buff = SMALL_BUFF) + group.add(parens) + groups.add(group) + arrow = TexMobject("\\Leftrightarrow").scale(2) + groups.submobjects.insert(1, arrow) + groups.arrange_submobjects(RIGHT) + groups.next_to(morty, UP+RIGHT, LARGE_BUFF) + groups.shift_onto_screen() + + + self.play(PiCreatureBubbleIntroduction( + morty, "Heisenberg \\\\ uncertainty \\\\ principle", + bubble_class = ThoughtBubble, + bubble_kwargs = {"height" : 4, "width" : 4, "direction" : RIGHT}, + target_mode = "pondering" + )) + self.wait() + self.play(morty.change, "confused", dot_gdw) + self.wait(10) + self.play( + ApplyMethod( + VGroup(dot_gdw, vector_gdw ).shift, + SPACE_WIDTH*RIGHT, + rate_func = running_start + ) + ) + self.remove(*continual_anims) + self.play( + morty.change, "raise_left_hand", groups, + FadeIn( + groups, + submobject_mode = "lagged_start", + run_time = 3, + ) + ) + self.wait(2) + +# End things + +class PatreonMention(PatreonThanks): + def construct(self): + morty = Mortimer() + morty.next_to(ORIGIN, DOWN) + + patreon_logo = PatreonLogo() + patreon_logo.to_edge(UP) + + thank_you = TextMobject("Thank you.") + thank_you.next_to(patreon_logo, DOWN) + + self.play( + DrawBorderThenFill(patreon_logo), + morty.change, "gracious" + ) + self.play(Write(thank_you)) + self.wait(3) + +class Promotion(PiCreatureScene): + CONFIG = { + "camera_class" : ThreeDCamera, + "seconds_to_blink" : 5, + } + def construct(self): + aops_logo = AoPSLogo() + aops_logo.next_to(self.pi_creature, UP+LEFT) + url = TextMobject( + "AoPS.com/", "3b1b", + arg_separator = "" + ) + url.to_corner(UP+LEFT) + url_rect = Rectangle(color = BLUE) + url_rect.replace( + url.get_part_by_tex("3b1b"), + stretch = True + ) + + url_rect.stretch_in_place(1.1, dim = 1) + + rect = Rectangle(height = 9, width = 16) + rect.scale_to_fit_height(4.5) + rect.next_to(url, DOWN) + rect.to_edge(LEFT) + rect.set_stroke(width = 0) + mathy = Mathematician() + mathy.flip() + mathy.to_corner(DOWN+RIGHT) + morty = self.pi_creature + morty.save_state() + book = ImageMobject("AoPS_volume_2") + book.scale_to_fit_height(2) + book.next_to(mathy, UP+LEFT).shift(MED_LARGE_BUFF*LEFT) + mathy.get_center = mathy.get_top + + words = TextMobject(""" + Interested in working for \\\\ + one of my favorite math\\\\ + education companies? + """, alignment = "") + words.to_edge(UP) + + arrow = Arrow( + aops_logo.get_top(), + morty.get_top(), + path_arc = -0.4*TAU, + use_rectangular_stem = False, + stroke_width = 5, + tip_length = 0.5, + ) + arrow.tip.shift(SMALL_BUFF*DOWN) + + self.add(words) + self.play( + self.pi_creature.change_mode, "raise_right_hand", + *[ + DrawBorderThenFill( + submob, + run_time = 2, + rate_func = squish_rate_func(double_smooth, a, a+0.5) + ) + for submob, a in zip(aops_logo, np.linspace(0, 0.5, len(aops_logo))) + ] + ) + self.play( + words.scale, 0.75, + words.next_to, url, DOWN, LARGE_BUFF, + words.shift_onto_screen, + Write(url), + ) + self.wait(2) + self.play( + LaggedStart( + ApplyFunction, aops_logo, + lambda mob : (lambda m : m.shift(0.2*UP).highlight(YELLOW), mob), + rate_func = there_and_back, + run_time = 1, + ), + morty.change, "thinking" + ) + self.wait() + self.play(ShowCreation(arrow)) + self.play(FadeOut(arrow)) + self.wait() + + # To teacher + self.play( + morty.change_mode, "plain", + morty.flip, + morty.scale, 0.7, + morty.next_to, mathy, LEFT, LARGE_BUFF, + morty.to_edge, DOWN, + FadeIn(mathy), + ) + self.play( + PiCreatureSays( + mathy, "", + bubble_kwargs = {"width" : 5}, + look_at_arg = morty.eyes, + ), + morty.change, "happy", + aops_logo.shift, 1.5*UP + 0.5*RIGHT + ) + self.play(Blink(mathy)) + self.wait() + self.play( + RemovePiCreatureBubble( + mathy, target_mode = "raise_right_hand" + ), + aops_logo.to_corner, UP+RIGHT, + aops_logo.shift, MED_SMALL_BUFF*DOWN, + GrowFromPoint(book, mathy.get_corner(UP+LEFT)), + ) + self.play(morty.change, "pondering", book) + self.wait(3) + self.play(Blink(mathy)) + self.wait() + self.play( + Animation( + BackgroundRectangle(book, fill_opacity = 1), + remover = True + ), + FadeOut(book), + ) + print self.num_plays + self.play( + FadeOut(words), + ShowCreation(rect), + morty.restore, + morty.change, "happy", rect, + FadeOut(mathy), + ) + self.wait(10) + self.play(ShowCreation(url_rect)) + self.play( + FadeOut(url_rect), + url.get_part_by_tex("3b1b").highlight, BLUE, + ) + self.wait(15) + +class PuzzleStatement(Scene): + def construct(self): + aops_logo = AoPSLogo() + url = TextMobject("AoPS.com/3b1b") + url.next_to(aops_logo, UP) + group = VGroup(aops_logo, url) + group.to_edge(UP) + self.add(group) + + words = TextMobject(""" + AoPS must choose one of 20 people to send to a + tug-of-war tournament. We don't care who we send, + as long as we don't send our weakest person. \\\\ \\\\ + + Each person has a different strength, but we don't know + those strengths. We get 10 intramural 10-on-10 matches + to determine who we send. Can we make sure we don't send + the weakest person? + """, alignment = "") + words.scale_to_fit_width(2*SPACE_WIDTH - 2) + words.next_to(group, DOWN, LARGE_BUFF) + self.play(LaggedStart(FadeIn, words, run_time = 5, lag_ratio = 0.2)) + self.wait(2) + +class UncertaintyEndScreen(PatreonEndScreen): + CONFIG = { + "specific_patrons" : [ + "CrypticSwarm", + "Ali Yahya", + "Juan Benet", + "Markus Persson", + "Damion Kistler", + "Burt Humburg", + "Yu Jun", + "Dave Nicponski", + "Kaustuv DeBiswas", + "Joseph John Cox", + "Luc Ritchie", + "Achille Brighton", + "Rish Kundalia", + "Yana Chernobilsky", + "Shìmín Kuang", + "Mathew Bramson", + "Jerry Ling", + "Mustafa Mahdi", + "Meshal Alshammari", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Robert Teed", + "Samantha D. Suplee", + "Mark Govea", + "John Haley", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Desmos ", + "Boris Veselinovich", + "Ryan Dahl", + "Ripta Pasay", + "Eric Lavault", + "Randall Hunt", + "Andrew Busey", + "Mads Elvheim", + "Tianyu Ge", + "Awoo", + "Dr. David G. Stork", + "Linh Tran", + "Jason Hise", + "Bernd Sing", + "James H. Park", + "Ankalagon ", + "Mathias Jansson", + "David Clark", + "Ted Suzman", + "Eric Chow", + "Michael Gardner", + "David Kedmey", + "Jonathan Eppele", + "Clark Gaebel", + "Jordan Scales", + "Ryan Atallah", + "supershabam ", + "1stViewMaths", + "Jacob Magnuson", + "Chloe Zhou", + "Ross Garber", + "Thomas Tarler", + "Isak Hietala", + "Egor Gumenuk", + "Waleed Hamied", + "Oliver Steele", + "Yaw Etse", + "David B", + "Delton Ding", + "James Thornton", + "Felix Tripier", + "Arthur Zey", + "George Chiesa", + "Norton Wang", + "Kevin Le", + "Alexander Feldman", + "David MacCumber", + "Jacob Kohl", + "Frank Secilia", + "George John", + "Akash Kumar", + "Britt Selvitelle", + "Jonathan Wilson", + "Michael Kunze", + "Giovanni Filippi", + "Eric Younge", + "Prasant Jagannath", + "Andrejs olins", + "Cody Brocious", + ], + } + +class Thumbnail(Scene): + def construct(self): + uncertainty_principle = TextMobject("Uncertainty \\\\", "principle") + uncertainty_principle[1].shift(SMALL_BUFF*UP) + quantum = TextMobject("Quantum") + VGroup(uncertainty_principle, quantum).scale(2.5) + uncertainty_principle.to_edge(UP, MED_LARGE_BUFF) + quantum.to_edge(DOWN, MED_LARGE_BUFF) + + arrow = TexMobject("\\Downarrow") + arrow.scale(4) + arrow.move_to(Line( + uncertainty_principle.get_bottom(), + quantum.get_top(), + )) + + cross = Cross(arrow) + cross.set_stroke(RED, 20) + + is_word, not_word = is_not = TextMobject("is", "\\emph{NOT}") + is_not.scale(3) + is_word.move_to(arrow) + # is_word.shift(0.6*UP) + not_word.highlight(RED) + not_word.set_stroke(RED, 3) + not_word.rotate(10*DEGREES, about_edge = DOWN+LEFT) + not_word.next_to(is_word, DOWN, 0.1*SMALL_BUFF) + + dot_cloud = ProbabalisticDotCloud( + n_copies = 1000, + ) + dot_gdw = dot_cloud.gaussian_distribution_wrapper + # dot_gdw.rotate(3*DEGREES) + dot_gdw.rotate(25*DEGREES) + # dot_gdw.scale(2) + dot_gdw.scale(2) + # dot_gdw.move_to(quantum.get_bottom()+SMALL_BUFF*DOWN) + dot_gdw.move_to(quantum) + + + + def get_func(a): + return lambda t : 0.5*np.exp(-a*t**2)*np.cos(TAU*t) + axes = Axes( + x_min = -6, x_max = 6, + x_axis_config = {"unit_size" : 0.25} + ) + graphs = VGroup(*[ + axes.get_graph(get_func(a)) + for a in 10, 3, 1, 0.3, 0.1, + ]) + graphs.arrange_submobjects(DOWN, buff = 0.6) + graphs.to_corner(UP+LEFT) + graphs.gradient_highlight(BLUE_B, BLUE_D) + + frequency_axes = Axes( + x_min = 0, x_max = 2, + x_axis_config = {"unit_size" : 1} + ) + fourier_graphs = VGroup(*[ + get_fourier_graph( + frequency_axes, graph.underlying_function, + t_min = -10, t_max = 10, + ) + for graph in graphs + ]) + for graph, fourier_graph in zip(graphs, fourier_graphs): + fourier_graph.pointwise_become_partial(fourier_graph, 0.02, 0.06) + fourier_graph.scale(3) + fourier_graph.stretch(3, 1) + fourier_graph.move_to(graph) + fourier_graph.to_edge(RIGHT) + + self.add(graphs, fourier_graphs) + + + self.add(dot_cloud) + self.add( + uncertainty_principle, quantum, + ) + self.add(arrow, cross) + # self.add(is_word) + # self.add(is_not) + + + + diff --git a/scene/moving_camera_scene.py b/scene/moving_camera_scene.py new file mode 100644 index 00000000..5cb9696d --- /dev/null +++ b/scene/moving_camera_scene.py @@ -0,0 +1,23 @@ +from helpers import * + +from camera import MovingCamera +from scene import Scene +from topics.geometry import ScreenRectangle + +class MovingCameraScene(Scene): + def setup(self): + self.camera_frame = ScreenRectangle(height = 2*SPACE_HEIGHT) + self.camera_frame.set_stroke(width = 0) + self.camera = MovingCamera( + self.camera_frame, **self.camera_config + ) + return self + + def get_moving_mobjects(self, *animations): + # TODO: Code repetition from ZoomedScene + moving_mobjects = Scene.get_moving_mobjects(self, *animations) + if self.camera_frame in moving_mobjects: + # When the camera is moving, so is everything, + return self.mobjects + else: + return moving_mobjects \ No newline at end of file diff --git a/scene/zoomed_scene.py b/scene/zoomed_scene.py index 52a7403d..f7d49e65 100644 --- a/scene/zoomed_scene.py +++ b/scene/zoomed_scene.py @@ -122,6 +122,7 @@ class ZoomedScene(Scene): self.zoomed_camera.capture_mobjects( mobjects, **kwargs ) + def get_moving_mobjects(self, *animations): moving_mobjects = Scene.get_moving_mobjects(self, *animations) if self.zoom_activated and self.little_rectangle in moving_mobjects: diff --git a/topics/characters.py b/topics/characters.py index 5f454c00..46197d2a 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -575,6 +575,7 @@ class PiCreatureScene(Scene): time_to_blink = self.total_wait_time%self.seconds_to_blink == 0 if blink and self.any_pi_creatures_on_screen() and time_to_blink: self.blink() + self.num_plays -= 1 #This shouldn't count as an animation else: self.non_blink_wait() time -= 1