diff --git a/eoc/chapter4.py b/eoc/chapter4.py index 09157955..6ece758d 100644 --- a/eoc/chapter4.py +++ b/eoc/chapter4.py @@ -1319,46 +1319,438 @@ class ShoveXSquaredInSine(Scene): ) self.dither() -class ThreeLinesChainRule(Scene): +class ThreeLinesChainRule(ReconfigurableScene): CONFIG = { - "default_x" : 0.5, + "start_x" : 0.5, + "top_x" : 3, + "example_x" : 1.5, + "dx" : 0.1, "line_configs" : [ { + "func" : lambda x : x, + "func_label" : "x", + "triangle_color" : WHITE, + "center_y" : 3, "x_min" : 0, "x_max" : 3, "numbers_to_show" : range(4), + "numbers_with_elongated_ticks" : range(4), "tick_frequency" : 0.25, }, { + "func" : lambda x : x**2, + "func_label" : "x^2", + "triangle_color" : X_SQUARED_COLOR, + "center_y" : 0.5, "x_min" : 0, - "x_max" : 9, - "numbers_to_show" : range(0, 10, 2), - "tick_frequency" : 1, + "x_max" : 10, + "numbers_to_show" : range(0, 11), + "numbers_with_elongated_ticks" : range(0, 11, 1), + "tick_frequency" : 0.25, }, { + "func" : lambda x : np.sin(x**2), + "func_label" : "\\sin(x^2)", + "triangle_color" : SINE_COLOR, + "center_y" : -2, "x_min" : -2, "x_max" : 2, "numbers_to_show" : range(-2, 3), + "numbers_with_elongated_ticks" : range(-2, 3), "tick_frequency" : 0.25, }, ], - "line_width" : 6, + "line_width" : 8, + "triangle_height" : 0.25, } def construct(self): - lines_group = self.get_three_lines_group() + self.introduce_line_group() + self.draw_function_arrows() + self.talk_through_movement() + self.nudge_x() + self.give_example_of_meaning() - self.add(lines_group) + def introduce_line_group(self): + self.line_group = self.get_line_group(self.start_x) + lines, labels = self.line_group - def get_three_line_group(self, x): - number_lines = VGroup(*[ - self.get_line(x, **line_config) - for line_config in self.line_configs + for line in lines: + self.play(Write(line, run_time = 2)) + self.dither() + last_label = labels[0].copy() + last_label.to_corner(UP+LEFT) + last_label.set_fill(opacity = 0) + for label in labels: + self.play(ReplacementTransform( + last_label.copy(), label + )) + self.dither() + last_label = label + for x in 1, 0, self.start_x: + self.animate_x_change(x, run_time = 1) + self.dither() + + def draw_function_arrows(self): + lines, line_labels = self.line_group + labels = VGroup(*[ + TexMobject("(\\dots)^2").highlight(X_SQUARED_COLOR), + TexMobject("\\sin(\\dots)").highlight(SINE_COLOR) ]) - number_lines.arrange_submobjects(DOWN, buff = LARGE_BUFF) - return number_lines + arrows = VGroup() + for lines_subset, label in zip([lines[:2], lines[1:]], labels): + arrow = Arc(start_angle = np.pi/3, angle = -2*np.pi/3) + arrow.add_tip() + arrow.highlight(label.get_color()) + arrow.next_to(VGroup(*lines_subset)) + arrows.add(arrow) + label.next_to(arrow, RIGHT) + + self.play( + ShowCreation(arrow), + Write(label) + ) + self.dither() + self.arrows = arrows + self.arrow_labels = labels + + def talk_through_movement(self): + lines, labels = self.line_group + + self.animate_x_change(self.top_x, run_time = 4) + self.dither() + for label in labels[0], labels[1]: + oval = Circle(color = YELLOW) + oval.replace(label, stretch = True) + oval.scale(2.5) + oval.move_to(label.get_bottom()) + self.play(ShowCreation(oval)) + self.dither() + self.play(FadeOut(oval)) + sine_text = TexMobject("\\sin(9) \\approx 0.412") + sine_text.move_to(labels[-1][-1]) + sine_text.to_edge(DOWN) + sine_arrow = Arrow( + sine_text.get_top(), + labels[-1][0].get_bottom(), + buff = SMALL_BUFF, + ) + self.play( + FadeIn(sine_text), + ShowCreation(sine_arrow) + ) + self.dither(2) + self.play(*map(FadeOut, [sine_text, sine_arrow])) + self.animate_x_change(self.example_x, run_time = 3) + + def nudge_x(self): + lines, labels = self.line_group + def get_value_points(): + return [ + label[0].get_bottom() + for label in labels + ] + starts = get_value_points() + self.animate_x_change(self.example_x + self.dx, run_time = 0) + ends = get_value_points() + self.animate_x_change(self.example_x, run_time = 0) + + nudge_lines = VGroup() + braces = VGroup() + numbers = VGroup() + for start, end, line, label, config in zip(starts, ends, lines, labels, self.line_configs): + color = label[0].get_color() + nudge_line = Line(start, end) + nudge_line.set_stroke(color, width = 6) + brace = Brace(nudge_line, DOWN, buff = SMALL_BUFF) + brace.highlight(color) + func_label = config["func_label"] + if len(func_label) == 1: + text = "$d%s$"%func_label + else: + text = "$d(%s)$"%func_label + brace.text = brace.get_text(text, buff = SMALL_BUFF) + brace.text.highlight(color) + brace.add(brace.text) + + line.add(nudge_line) + nudge_lines.add(nudge_line) + braces.add(brace) + numbers.add(line.numbers) + line.remove(*line.numbers) + dx_brace, dx_squared_brace, dsine_brace = braces + + x_value = str(self.example_x) + x_value_label = TexMobject("=%s"%x_value) + x_value_label.next_to(labels[0][1], RIGHT) + dx_squared_value = TexMobject( + "= 2x\\,dx ", "\\\\ = 2(%s)dx"%x_value + ) + dx_squared_value.shift( + dx_squared_brace.text.get_right()+MED_SMALL_BUFF*RIGHT - \ + dx_squared_value[0].get_left() + ) + dsine_value = TextMobject( + "$=\\cos(%s)$"%self.line_configs[1]["func_label"], + dx_squared_brace.text.get_tex_string() + ) + dsine_value.next_to(dsine_brace.text) + less_than_zero = TexMobject("<0") + less_than_zero.next_to(dsine_brace.text) + + all_x_squared_relevant_labels = VGroup( + dx_squared_brace, dsine_brace, + labels[1], labels[2], + dsine_value, + ) + all_x_squared_relevant_labels.save_state() + + self.play(FadeOut(numbers)) + self.animate_x_change( + self.example_x + self.dx, + run_time = 1, + added_anims = it.chain( + [GrowFromCenter(dx_brace)], + map(ShowCreation, nudge_lines) + ) + ) + self.animate_x_change(self.example_x) + self.dither() + self.play(Write(x_value_label)) + self.dither() + self.play(FocusOn(dx_squared_brace)) + self.play(Write(dx_squared_brace)) + self.wiggle_by_dx() + self.dither() + for part in dx_squared_value: + self.play(Write(part)) + self.dither() + self.play(FadeOut(dx_squared_value)) + self.dither() + #Needs to be part of everything for the reconfiguraiton + dsine_brace.set_fill(opacity = 0) + dsine_value.set_fill(opacity = 0) + self.add(dsine_brace, dsine_value) + self.replace_x_squared_with_h() + self.dither() + self.play(dsine_brace.set_fill, None, 1) + self.discuss_dsine_sign(less_than_zero) + self.dither() + dsine_value.set_fill(opacity = 1) + self.play(Write(dsine_value)) + self.dither() + self.play( + all_x_squared_relevant_labels.restore, + submobject_mode = "lagged_start", + lag_factor = 3, + run_time = 3, + ) + self.__dict__.update(self.__class__.CONFIG) + self.dither() + + two_x_dx = dx_squared_value[0] + dx_squared = dsine_value[1] + two_x_dx_copy = VGroup(*two_x_dx[1:]).copy() + self.play(Write(two_x_dx)) + self.play( + two_x_dx_copy.move_to, dx_squared, LEFT, + dx_squared.next_to, dx_squared, UP, + ) + self.play(FadeOut(dx_squared)) + self.dither(2) + + self.final_derivative = dsine_value + + def discuss_dsine_sign(self, less_than_zero): + self.wiggle_by_dx() + self.dither() + for x in self.example_x+self.dx, self.example_x: + self.animate_x_change(x, run_time = 2) + self.dither() + if less_than_zero not in self.get_mobjects(): + self.play(Write(less_than_zero)) + else: + self.play(FadeOut(less_than_zero)) + + def replace_x_squared_with_h(self): + new_config = copy.deepcopy(self.__class__.CONFIG) + new_config["line_configs"][1]["func_label"] = "h" + new_config["line_configs"][2]["func_label"] = "\\sin(h)" + self.transition_to_alt_config( + return_to_original_configuration = False, + **new_config + ) + + def give_example_of_meaning(self): + words = TextMobject("For example,") + expression = TexMobject("\\cos(1.5^2)\\cdot 2(1.5)\\,dx") + group = VGroup(words, expression) + group.arrange_submobjects(DOWN, aligned_edge = LEFT) + group.scale(0.8) + group.to_edge(RIGHT) + arrow = Arrow(group.get_bottom(), self.final_derivative[0].get_top()) + + self.play(*map(FadeOut, [self.arrows, self.arrow_labels])) + self.play(FadeIn(group)) + self.play(ShowCreation(arrow)) + self.dither() + self.wiggle_by_dx() + self.dither() + + + ######## + + def wiggle_by_dx(self, **kwargs): + kwargs["run_time"] = kwargs.get("run_time", 1) + kwargs["rate_func"] = kwargs.get("rate_func", there_and_back) + target_x = self.line_group.x_val + self.dx + self.animate_x_change(target_x, **kwargs) + + def animate_x_change(self, target_x, **kwargs): + #Assume fixed lines, only update labels + kwargs["run_time"] = kwargs.get("run_time", 2) + added_anims = kwargs.get("added_anims", []) + start_x = self.line_group.x_val + def update(line_group, alpha): + lines, labels = line_group + new_x = interpolate(start_x, target_x, alpha) + for line, label, config in zip(lines, labels, self.line_configs): + new_label = self.get_line_label( + line, new_x, **config + ) + Transform(label, new_label).update(1) + line_group.x_val = new_x + self.play( + UpdateFromAlphaFunc(self.line_group, update), + *added_anims, + **kwargs + ) + + def get_line_group(self, x): + group = VGroup() + group.lines, group.labels = VGroup(), VGroup() + for line_config in self.line_configs: + number_line = self.get_number_line(**line_config) + label = self.get_line_label(number_line, x, **line_config) + group.lines.add(number_line) + group.labels.add(label) + group.add(group.lines, group.labels) + group.x_val = x + return group + + def get_number_line( + self, center_y, **number_line_config + ): + number_line = NumberLine(color = GREY, **number_line_config) + number_line.stretch_to_fit_width(self.line_width) + number_line.add_numbers() + number_line.shift(center_y*UP) + number_line.to_edge(LEFT, buff = LARGE_BUFF) + + return number_line + + def get_line_label( + self, number_line, x, func, func_label, triangle_color, + **spillover_kwargs + ): + triangle = RegularPolygon( + n=3, start_angle = -np.pi/2, + fill_color = triangle_color, + fill_opacity = 0.75, + stroke_width = 0, + ) + triangle.scale_to_fit_height(self.triangle_height) + triangle.move_to( + number_line.number_to_point(func(x)), DOWN + ) + + label_mob = TexMobject(func_label) + label_mob.next_to(triangle, UP, buff = SMALL_BUFF, aligned_edge = LEFT) + + return VGroup(triangle, label_mob) + +class GeneralizeChainRule(Scene): + def construct(self): + example = TexMobject( + "\\frac{d}{dx}", "\\sin(", "x^2", ")", "=", + "\\cos(", "x^2", ")", "\\,2x", + ) + general = TexMobject( + "\\frac{d}{dx}", "g(", "h(x)", ")", "=", + "{dg \\over ", " dh}(", "h(x)", ")", "{dh \\over", " dx}", "(x)" + ) + example.to_edge(UP, buff = LARGE_BUFF) + general.next_to(example, DOWN, buff = 1.5*LARGE_BUFF) + for mob in example, general: + mob.highlight(SINE_COLOR) + mob[0].highlight(WHITE) + for tex in "x^2", "2x", "(x)", "{dh", " dx}": + mob.highlight_by_tex(tex, X_SQUARED_COLOR, substring = True) + + example_outer = VGroup(*example[1:4]) + example_inner = example[2] + d_example_outer = VGroup(*example[5:8]) + d_example_inner = example[6] + d_example_d_inner = example[8] + + general_outer = VGroup(*general[1:4]) + general_inner = general[2] + d_general_outer = VGroup(*general[5:9]) + d_general_inner = general[7] + d_general_d_inner = VGroup(*general[9:12]) + + example_outer_brace = Brace(example_outer) + example_inner_brace = Brace(example_inner, buff = SMALL_BUFF) + d_example_outer_brace = Brace(d_example_outer) + d_example_inner_brace = Brace(d_example_inner, buff = SMALL_BUFF) + d_example_d_inner_brace = Brace(d_example_d_inner, UP, buff = SMALL_BUFF) + + general_outer_brace = Brace(general_outer) + general_inner_brace = Brace(general_inner, buff = SMALL_BUFF) + d_general_outer_brace = Brace(d_general_outer) + d_general_inner_brace = Brace(d_general_inner, buff = SMALL_BUFF) + d_general_d_inner_brace = Brace(d_general_d_inner, UP, buff = SMALL_BUFF) + + for brace in example_outer_brace, general_outer_brace: + brace.text = brace.get_text("Outer") + for brace in d_example_outer_brace, d_general_outer_brace: + brace.text = brace.get_text("d(Outer)") + brace.text.shift(SMALL_BUFF*LEFT) + for brace in d_example_d_inner_brace, d_general_d_inner_brace: + brace.text = brace.get_text("d(Inner)", buff = SMALL_BUFF) + + #d(out)d(in) for example + self.add(example) + outer_braces = VGroup(example_outer_brace, d_example_outer_brace) + self.play(*map(GrowFromCenter, outer_braces)) + for brace in outer_braces: + self.play(Write(brace.text, run_time = 1)) + self.dither() + inner_braces = VGroup(example_inner_brace, d_example_inner_brace) + self.play(*it.chain(*[ + [mob.scale_in_place, 1.2, mob.highlight, YELLOW] + for mob in example_inner, d_example_inner + ]), rate_func = there_and_back) + self.play(Transform( + example_inner.copy(), d_example_inner, + path_arc = -np.pi/2, + remover = True + )) + self.dither() + self.play( + GrowFromCenter(d_example_d_inner_brace), + Write(d_example_d_inner_brace.text) + ) + self.play(Transform( + VGroup(*reversed(example_inner.copy())), + d_example_d_inner, + path_arc = -np.pi/2, + run_time = 2, + remover = True + )) + self.dither() + + #Generalize - def get_number_line(self, x, **line_config): - number_line = NumberLine(**line_config) diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index fb5fdf72..103dca69 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -85,10 +85,11 @@ class TexMobject(SVGMobject): tex.count(char) for char in "{}" ] - if tex.startswith("{") and num_lefts > num_rights: - return tex[1:] - elif tex.endswith("}") and num_rights > num_lefts: - return tex[:-1] + if num_rights > num_lefts: + backwards = tex[::-1].replace("}", "", num_rights-num_lefts) + tex = backwards[::-1] + elif num_lefts > num_rights: + tex = tex.replace("{", "", num_lefts-num_rights) return tex @@ -109,13 +110,13 @@ class TexMobject(SVGMobject): self.submobjects = new_submobjects return self - def highlight_by_tex(self, tex, color): + def highlight_by_tex(self, tex, color, substring = True): if not hasattr(self, "expression_parts"): if tex == self.get_tex_string(): self.highlight(color) return self for submob, part_tex in zip(self.split(), self.expression_parts): - if part_tex == tex: + if (part_tex == tex) or (substring and tex in part_tex): submob.highlight(color) return self