diff --git a/bell.py b/bell.py index 1efe1e2e..b2a811c5 100644 --- a/bell.py +++ b/bell.py @@ -27,25 +27,518 @@ from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * - - - - - - - - - - - - - - - - - - - +from waves import * + +#force_skipping +#revert_to_original_skipping_status + +class FilterScene(ThreeDScene): + CONFIG = { + "filter_x_coordinates" : [0], + "pol_filter_configs" : [{}], + "EMWave_config" : { + "start_point" : SPACE_WIDTH*LEFT + DOWN+OUT + }, + "start_phi" : 0.8*np.pi/2, + "start_theta" : -0.6*np.pi, + "ambient_rotation_rate" : 0.01, + } + def setup(self): + self.axes = ThreeDAxes() + self.add(self.axes) + for x in range(len(self.filter_x_coordinates) - len(self.pol_filter_configs)): + self.pol_filter_configs.append({}) + self.pol_filters = VGroup(*[ + PolarizingFilter(**config) + for config in self.pol_filter_configs + ]) + self.pol_filters.rotate(np.pi/2, RIGHT) + self.pol_filters.rotate(-np.pi/2, OUT) + self.pol_filters.shift(DOWN+OUT) + for x, pf in zip(self.filter_x_coordinates, self.pol_filters): + pf.shift(x*RIGHT) + self.add(self.pol_filters) + self.pol_filter = self.pol_filters[0] + + self.set_camera_position(self.start_phi, self.start_theta) + if self.ambient_rotation_rate > 0: + self.begin_ambient_camera_rotation(self.ambient_rotation_rate) + + def get_filter_absorbtion_animation(self, pol_filter, photon): + x = pol_filter.get_center()[0] + alpha = (x + SPACE_WIDTH) / (2*SPACE_WIDTH) + return ApplyMethod( + pol_filter.set_fill, RED, + run_time = photon.run_time, + rate_func = squish_rate_func(there_and_back, alpha - 0.1, alpha + 0.1) + ) + +class PhotonPassesCompletelyOrNotAtAll(FilterScene): + CONFIG = { + "pol_filter_configs" : [{ + "include_arrow_label" : False, + "label_tex" : "\\text{Filter}", + }], + "start_theta" : -0.9*np.pi, + "target_theta" : -0.6*np.pi, + } + def construct(self): + pol_filter = self.pol_filter + label = pol_filter.label + pol_filter.remove(label) + label.shift(SMALL_BUFF*IN) + + passing_words = TextMobject("Photon", "passes through") + passing_words.highlight(GREEN) + filtered_words = TextMobject("Photon", "is blocked") + filtered_words.highlight(RED) + for words in passing_words, filtered_words: + words.next_to(ORIGIN, UP+LEFT) + words.shift(2*UP) + words.add_background_rectangle() + words.rotate(np.pi/2, RIGHT) + + passing_photon = WavePacket( + run_time = 3, + get_filtered = False, + EMWave_config = self.EMWave_config + ) + new_em_wave_config = dict(self.EMWave_config) + new_em_wave_config["A_x"] = 0 + new_em_wave_config["A_y"] = 1 + filtered_photon = WavePacket( + run_time = 3, + get_filtered = True, + EMWave_config = new_em_wave_config + ) + + + self.play( + DrawBorderThenFill(pol_filter), + Write(label, run_time = 2) + ) + self.move_camera(theta = self.target_theta) + self.play(Write(passing_words, run_time = 1)) + self.play(passing_photon) + self.play(Transform(passing_words, filtered_words)) + self.play( + filtered_photon, + ApplyMethod( + pol_filter.set_fill, RED, + rate_func = squish_rate_func(there_and_back, 0.4, 0.6), + run_time = filtered_photon.run_time + ) + ) + self.dither(3) + +class DirectionOfPolarization(FilterScene): + CONFIG = { + "pol_filter_configs" : [{ + "include_arrow_label" : False, + }], + "target_theta" : -0.97*np.pi, + "target_phi" : 0.9*np.pi/2, + "ambient_rotation_rate" : 0.005, + "apply_filter" : True, + } + def setup(self): + self.reference_line = Line(ORIGIN, RIGHT) + self.reference_line.set_stroke(width = 0) + self.em_wave = EMWave(**self.EMWave_config) + self.add(self.em_wave) + + FilterScene.setup(self) + + def construct(self): + self.remove(self.pol_filter) + words = TextMobject("Polarization direction") + words.next_to(ORIGIN, UP+RIGHT, LARGE_BUFF) + words.shift(2*UP) + words.rotate(np.pi/2, RIGHT) + words.rotate(-np.pi/2, OUT) + + em_wave = self.em_wave + + self.add(em_wave) + self.dither(2) + self.move_camera( + phi = self.target_phi, + theta = self.target_theta + ) + self.play(Write(words, run_time = 1)) + self.change_polarization_direction( + 2*np.pi/3, + run_time = 6, + rate_func = there_and_back + ) + self.dither(2) + + def change_polarization_direction(self, angle, **kwargs): + added_anims = kwargs.get("added_anims", []) + self.play( + ApplyMethod( + self.reference_line.rotate, angle, + **kwargs + ), + *added_anims + ) + + def continual_update(self): + FilterScene.continual_update(self) + wave = self.em_wave.mobject + angle = self.reference_line.get_angle() + wave.rotate( + angle, self.em_wave.propogation_direction, + about_point = self.em_wave.start_point, + ) + if self.apply_filter: + filter_x = self.pol_filter.get_center()[0] + for vect_group, angle in (self.em_wave.E_vects, 0), (self.em_wave.M_vects, np.pi/2): + proj_vect = rotate_vector( + OUT, self.pol_filter.filter_angle + angle, RIGHT, + ) + proj_matrix = np.array([RIGHT] + [ + proj_vect*np.dot(proj_vect, basis) + for basis in UP, OUT + ]).T + for vect in vect_group: + start, end = vect.get_start_and_end() + if start[0] > filter_x: + vect.apply_matrix(proj_matrix) + vect.shift(start - vect.get_start()) + + +class PhotonsThroughPerpendicularFilters(DirectionOfPolarization): + CONFIG = { + "filter_x_coordinates" : [-2, 2], + "pol_filter_configs" : [ + {"filter_angle" : 0}, + {"filter_angle" : np.pi/2}, + ], + "start_theta" : -0.9*np.pi, + "target_theta" : -0.6*np.pi, + "EMWave_config" : { + "wave_number" : 0, + } + } + def setup(self): + DirectionOfPolarization.setup(self) + self.remove(self.em_wave) + + def construct(self): + photons = self.get_photons()[:2] + prob_text = self.get_probability_text() + self.pol_filters = VGroup(*reversed(self.pol_filters)) + + self.play(LaggedStart(DrawBorderThenFill, self.pol_filters)) + self.move_camera( + theta = self.target_theta, + added_anims = [ + FadeIn(prob_text) + ] + ) + for x in range(4): + pairs = zip(photons, self.pol_filters) + random.shuffle(pairs) + for photon, pol_filter in pairs: + self.play( + photon, + self.get_filter_absorbtion_animation( + pol_filter, photon + ) + ) + + def get_photons(self): + self.reference_line.rotate(np.pi/4) + self.continual_update() + return [ + WavePacket( + filter_distance = SPACE_WIDTH + x, + get_filtered = True, + em_wave = self.em_wave.copy(), + run_time = 1.5, + ) + for x in 2, -2, 10 + ] + + def get_probability_text(self, prob = 0): + prob_text = TexMobject( + "P(", "\\substack", "{\\text{photons that make it} \\\\ ", + " \\text{here } ", "\\text{make it}", + " \\text{ here} }", ")", "=", str(int(prob*100)), "\\%", + arg_separator = "" + ) + here1, here2 = prob_text.get_parts_by_tex("here") + here1.highlight(GREEN) + here2.highlight(RED) + prob_text.add_background_rectangle() + prob_text.next_to(ORIGIN, UP+RIGHT) + prob_text.shift(2.5*UP+LEFT) + prob_text.rotate(np.pi/2, RIGHT) + arrows = [ + Arrow( + here.get_edge_center(IN), + DOWN+OUT + x*RIGHT, + color = here.get_color(), + normal_vector = DOWN+OUT, + ) + for here, x in (here1, 0), (here2, 4) + ] + prob_text.add(*arrows) + + return prob_text + +class AngryPiCreature(PiCreatureScene): + def construct(self): + self.pi_creature_says( + "No, \\emph{locality} \\\\ must be wrong!", + target_mode = "angry", + look_at_arg = 2*RIGHT, + run_time = 1 + ) + self.dither(3) + + def create_pi_creature(self): + return Randolph().shift(DOWN+3*LEFT) + +class ShowALittleMath(TeacherStudentsScene): + def construct(self): + expression = TexMobject( + "\\alpha", "| \\! \\uparrow \\rangle", "+", + "\\beta", "| \\! \\rightarrow \\rangle", + ) + expression.highlight_by_tex("uparrow", GREEN) + expression.highlight_by_tex("rightarrow", RED) + expression.next_to(self.teacher, UP+LEFT, LARGE_BUFF) + + prob = TexMobject("\\text{Probability}", "=", "|", "\\alpha", "|^2") + prob.next_to(expression, UP, LARGE_BUFF) + + self.play( + Write(expression), + self.teacher.change, "raise_right_hand" + ) + target_alpha = prob.get_part_by_tex("alpha") + prob.remove(target_alpha) + self.play( + ReplacementTransform( + expression.get_part_by_tex("alpha").copy(), + target_alpha, + ), + Write(prob) + ) + self.change_student_modes( + *["pondering"]*3, + look_at_arg = prob + ) + self.dither(2) + +class SecondVideoWrapper(Scene): + def construct(self): + title = TextMobject("Some light quantum mechanics") + title.to_edge(UP) + self.add(title) + + screen_rect = ScreenRectangle(height = 6) + screen_rect.next_to(title, DOWN) + self.play(ShowCreation(screen_rect)) + self.dither(3) + +class BasicsOfPolarization(DirectionOfPolarization): + def construct(self): + self.show_continual_wave() + self.show_photons() + + def show_continual_wave(self): + em_wave = self.em_wave + em_wave.M_vects.set_fill(opacity = 0.5) + + title = TextMobject("Waves in the ``electromagnetic field''") + title.to_edge(UP) + subtitle = TextMobject("Polarization = Direction of", "wiggling") + subtitle.highlight_by_tex("wiggling", YELLOW) + subtitle.next_to(title, DOWN) + for words in title, subtitle: + words.add_background_rectangle() + words.rotate(np.pi/2, RIGHT) + + self.play(Write(title)) + self.dither(2) + self.play(Write(subtitle, run_time = 2)) + self.change_polarization_direction(np.pi/2, run_time = 3) + self.dither(2) + self.change_polarization_direction(-np.pi/12, run_time = 2) + self.dither() + self.move_camera(theta = -0.95*np.pi) + self.dither() + self.change_polarization_direction(-np.pi/6, run_time = 2) + self.dither() + self.move_camera(theta = -0.6*np.pi) + self.dither() + self.play(FadeOut(em_wave.mobject)) + self.remove(em_wave) + self.reference_line.put_start_and_end_on(ORIGIN, RIGHT) + + def show_photons(self): + quantum_left_words = TextMobject( + "Quantum", "$\\Rightarrow$", + ) + quantum_left_words.next_to(ORIGIN, UP+RIGHT) + quantum_left_words.shift(UP) + quantum_right_words = TextMobject( + "Completely through", "or \\\\", + "Completely blocked", + ) + quantum_right_words.scale(0.8) + quantum_right_words.next_to(quantum_left_words, buff = 0) + quantum_right_words.highlight_by_tex("through", GREEN) + quantum_right_words.highlight_by_tex("blocked", RED) + quantum_words = VGroup(quantum_left_words, quantum_right_words) + quantum_words.rotate(np.pi/2, RIGHT) + + config = dict(self.EMWave_config) + config.update({ + "wave_number" : 0, + "A_x" : -1, + "A_y" : 1, + }) + self.em_wave = EMWave(**config) + self.continual_update() + passing_photon = WavePacket( + em_wave = self.em_wave.copy(), + run_time = 2, + ) + filtered_photon = WavePacket( + em_wave = self.em_wave.copy(), + get_filtered = True, + run_time = 2, + ) + + self.play(FadeIn( + quantum_words, + run_time = 2, + submobject_mode = "lagged_start" + )) + anim_sets = [ + [passing_photon], + [ + filtered_photon, + self.get_filter_absorbtion_animation( + self.pol_filter, + filtered_photon + ) + ], + ] + for index in 0, 1, 0, 0, 1: + self.play(*anim_sets[index]) + +class AngleToProbabilityChart(Scene): + def construct(self): + TextMobject("Angle between filters") + TextMobject( + "Probability that one" + ) + +class ShowVariousFilterPairs(PhotonsThroughPerpendicularFilters): + CONFIG = { + "filter_x_coordinates" : [-2, 2, 2, 2, 2], + "pol_filter_configs" : [ + {"filter_angle" : angle} + for angle in 0, 0, np.pi/2, np.pi/4, np.pi/8 + ], + } + def construct(self): + self.photons = self.get_photons() + + self.add_filters() + self.add_probability_text() + self.show_photons() + self.revert_to_original_skipping_status() + for pol_filter in self.pol_filters[2:]: + self.change_to_new_filter(pol_filter) + self.show_photons() + + def add_filters(self): + self.remove(*self.pol_filters[1:]) + self.dither() + self.play(ReplacementTransform( + self.pol_filters[0].copy().set_fill(BLACK, 1), + self.pol_filters[1] + )) + self.move_camera( + theta = -0.6*np.pi, + added_anims = list(it.chain(*[ + [ + pf.arrow_label.rotate_in_place, np.pi/2, OUT, + pf.arrow_label.next_to, pf.arrow, RIGHT + ] + for pf in self.pol_filters[:2] + ])) + ) + for pf in self.pol_filters[2:]: + pf.arrow_label.rotate_in_place(np.pi/2, OUT) + pf.arrow_label.next_to(pf.arrow, RIGHT) + + self.second_filter = self.pol_filters[1] + self.add_foreground_mobject(self.second_filter) + + def add_probability_text(self): + prob_text = self.get_probability_text(self.get_prob()) + self.play(FadeIn(prob_text)) + self.prob_text = prob_text + + def show_photons(self, n_photons = 5): + p = self.get_prob() + photons = [ + copy.deepcopy(self.photons[2 if random.random() < p else 0]) + for x in range(n_photons) + ] + for photon in photons: + added_anims = [] + if photon.filter_distance == 2: + added_anims.append(self.get_filter_absorbtion_animation( + self.second_filter, photon + )) + self.play(photon, *added_anims, run_time = 1.5) + self.dither() + + def change_to_new_filter(self, pol_filter): + self.play(Transform(self.second_filter, pol_filter)) + self.second_filter.filter_angle = pol_filter.filter_angle + new_prob_text = self.get_probability_text(self.get_prob()) + new_prob_text[1][-2].highlight(YELLOW) + self.play(Transform(self.prob_text, new_prob_text)) + + #### + + def get_prob(self): + return np.cos(self.second_filter.filter_angle)**2 + + +class ForgetPreviousActions(PhotonsThroughPerpendicularFilters): + CONFIG = { + "filter_x_coordinates" : [-6, -2, 2], + "pol_filter_configs" : [ + {"filter_angle" : angle} + for angle in np.pi/4, 0, np.pi/4 + ], + "start_theta" : -0.6*np.pi + } + def construct(self): + for pf in self.pol_filters: + pf.arrow_label.rotate_in_place(np.pi/2, OUT) + pf.arrow_label.next_to(pf.arrow, RIGHT) + + rect = SurroundingRectangle(VGroup(*self.pol_filters[1:])) + rect.rotate_in_place(np.pi/2, RIGHT) + rect.stretch_ + words = TextMobject( + "Probabilities depend only\\\\", + "on this angle difference" + ) + words.rotate(np.pi/2, RIGHT) + words.next_to(rect, OUT) + self.add(rect, words) diff --git a/mobject/mobject.py b/mobject/mobject.py index 39544327..4c3ed6cf 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -82,14 +82,15 @@ class Mobject(object): setattr(self, attr, func(getattr(self, attr))) return self - def get_image(self): - from camera import Camera - camera = Camera() + def get_image(self, camera = None): + if camera is None: + from camera import Camera + camera = Camera() camera.capture_mobject(self) return Image.fromarray(camera.get_image()) - def show(self): - self.get_image().show() + def show(self, camera = None): + self.get_image(camera = camera).show() def save_image(self, name = None): self.get_image().save( diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index b77b437f..088b8ec0 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -82,6 +82,8 @@ class TexMobject(SVGMobject): tex += "\\," if tex == "\\sqrt": tex += "{\\quad}" + if tex == "\\substack": + tex = "" for t1, t2 in ("\\left", "\\right"), ("\\right", "\\left"): should_replace = reduce(op.and_, [ t1 in tex, diff --git a/text_template.tex b/text_template.tex index 8244e3a4..fcdba26c 100644 --- a/text_template.tex +++ b/text_template.tex @@ -13,6 +13,7 @@ \usepackage{calligra} \usepackage{wasysym} \usepackage{ragged2e} +\usepackage{physics} \begin{document} diff --git a/topics/geometry.py b/topics/geometry.py index 8932eea6..356a7229 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -182,7 +182,8 @@ class Arrow(Line): "color" : YELLOW_C, "tip_length" : 0.25, "tip_width_to_length_ratio" : 1, - "max_tip_length_to_length_ratio" : 0.25, + "max_tip_length_to_length_ratio" : 0.35, + "max_stem_width_to_tip_width_ratio" : 0.3, "buff" : MED_SMALL_BUFF, "propogate_style_to_family" : False, "preserve_tip_size_when_scaling" : True, @@ -229,8 +230,12 @@ class Arrow(Line): tip_base = center_of_mass(tip_base_points) tbp1, tbp2 = tip_base_points perp_vect = tbp2 - tbp1 - perp_vect /= np.linalg.norm(perp_vect) - width = self.rectangular_stem_width + tip_base_width = np.linalg.norm(perp_vect) + perp_vect /= tip_base_width + width = min( + self.rectangular_stem_width, + self.max_stem_width_to_tip_width_ratio*tip_base_width, + ) self.rect.set_points_as_corners([ tip_base + perp_vect*width/2, start + perp_vect*width/2, diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 6c3cd0d8..e9b9a1aa 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -144,7 +144,7 @@ class ThreeDScene(Scene): def set_camera_position(self, phi = None, theta = None, distance = None): self.camera.set_position(phi, theta, distance) - def begin_ambient_camera_rotation(self, rate = -0.01*np.pi): + def begin_ambient_camera_rotation(self, rate = 0.01): self.ambient_camera_rotation = AmbientRotation( self.camera.position_mobject, axis = OUT, diff --git a/waves.py b/waves.py index 1695a7c4..26fadad7 100644 --- a/waves.py +++ b/waves.py @@ -29,7 +29,7 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * E_COLOR = BLUE -M_COLOR = RED +M_COLOR = YELLOW class OscillatingVector(ContinualAnimation): CONFIG = { @@ -107,7 +107,7 @@ class OscillatingVectorComponents(ContinualAnimationGroup): class EMWave(ContinualAnimationGroup): CONFIG = { - "wave_number" : 3, + "wave_number" : 1, "frequency" : 0.25, "n_vectors" : 40, "propogation_direction" : RIGHT, @@ -134,7 +134,7 @@ class EMWave(ContinualAnimationGroup): for alpha in np.linspace(0, 1, self.n_vectors): tail = interpolate(ORIGIN, self.length*OUT, alpha) - phase = alpha*self.length*self.wave_number + phase = -alpha*self.length*self.wave_number E_ov = OscillatingVector( Vector(UP, color = E_COLOR), tail = np.array(tail), @@ -163,10 +163,6 @@ class EMWave(ContinualAnimationGroup): def update_mobject(self, dt): ContinualAnimationGroup.update_mobject(self, dt) - # for vect in self.E_vects: - # vect.rotate_in_place(np.pi/2, RIGHT) - # for vect in self.M_vects: - # vect.rotate_in_place(np.pi/2, UP) self.mobject.rotate(self.rotation, OUT) self.mobject.apply_matrix(self.matrix_transform) self.mobject.shift(self.start_point) @@ -176,7 +172,10 @@ class WavePacket(Animation): "EMWave_config" : { "wave_number" : 0, "start_point" : SPACE_WIDTH*LEFT, + "phi_x" : np.pi/4, + "phi_y" : np.pi/4, }, + "em_wave" : None, "run_time" : 4, "rate_func" : None, "packet_width" : 6, @@ -184,12 +183,15 @@ class WavePacket(Animation): "include_M_vects" : True, "filter_distance" : SPACE_WIDTH, "get_filtered" : False, + "remover" : True, } def __init__(self, **kwargs): digest_config(self, kwargs) - em_wave = EMWave(**self.EMWave_config) - em_wave.update(0) - self.em_wave = em_wave + em_wave = self.em_wave + if em_wave is None: + em_wave = EMWave(**self.EMWave_config) + em_wave.update(0) + self.em_wave = em_wave self.vects = VGroup() if self.include_E_vects: @@ -219,17 +221,15 @@ class WavePacket(Animation): tail - packet_center, em_wave.propogation_direction ) - A = em_wave.amplitude*self.func(distance_from_packet) - if np.abs(A) < 0.1: - A = 0 + A = em_wave.amplitude*self.E_func(distance_from_packet) distance_from_start = np.linalg.norm(tail - em_wave.start_point) if self.get_filtered and distance_from_start > self.filter_distance: A = 0 vect.restore() vect.scale(A/vect.get_length(), about_point = tail) - def func(self, x): - return np.sin(x)*np.exp(-0.5*x*x) + def E_func(self, x): + return np.sin(2*x)*np.exp(-0.25*x*x) class FilterLabel(TexMobject): def __init__(self, tex, degrees, **kwargs): @@ -243,6 +243,7 @@ class PolarizingFilter(Circle): "fill_opacity" : 0.5, "label_tex" : None, "filter_angle" : 0, + "include_arrow_label" : True, } def __init__(self, **kwargs): Circle.__init__(self, **kwargs) @@ -252,25 +253,31 @@ class PolarizingFilter(Circle): self.label.next_to(self.get_top(), DOWN, SMALL_BUFF) self.add(self.label) - arrow = Arrow(ORIGIN, MED_LARGE_BUFF*UP, buff = 0) + arrow = Arrow( + ORIGIN, 0.7*UP, + color = WHITE, + buff = 0, + ) arrow.shift(self.get_top()) arrow.rotate(-self.filter_angle) self.add(arrow) self.arrow = arrow + shade_in_3d(self) - arrow_label = TexMobject( - "%.1f^\\circ"%(self.filter_angle*180/np.pi) - ) - arrow_label.next_to(arrow.get_tip(), UP) - self.add(arrow_label) - self.arrow_label = arrow_label + if self.include_arrow_label: + arrow_label = TexMobject( + "%.1f^\\circ"%(self.filter_angle*180/np.pi) + ) + arrow_label.add_background_rectangle() + arrow_label.next_to(arrow.get_tip(), UP) + self.add(arrow_label) + self.arrow_label = arrow_label class EMScene(Scene): def construct(self): pass - -class TestCircularPolarization(ThreeDScene): +class TestPhoton(ThreeDScene): def construct(self): self.add(ThreeDAxes()) @@ -288,20 +295,15 @@ class TestCircularPolarization(ThreeDScene): # shade_in_3d(pol_filter) # self.add(pol_filter) - wave = EMWave(wave_number = 1, A_y = 1, phi_y = np.pi/2) - shade_in_3d(wave.mobject) - self.add(wave) - self.dither(20) - # self.dither() - # self.move_camera(theta = -1.2*np.pi/2) - # self.play(WavePacket( - # run_time = 3, - # get_filtered = True, - # EMWave_config = { - # "start_point" : SPACE_WIDTH*LEFT + DOWN+OUT - # } - # )) - # self.dither() + self.move_camera(theta = -1.2*np.pi/2) + self.play(WavePacket( + run_time = 2, + # get_filtered = True, + EMWave_config = { + "start_point" : SPACE_WIDTH*LEFT + DOWN+OUT + } + )) + self.dither()