text: from manimlib.imports import *
  
  ADDER_COLOR = GREEN
  MULTIPLIER_COLOR = YELLOW
  
  def normalize(vect):
      norm = get_norm(vect)
      if norm == 0:
          return OUT
      else:
          return vect/norm
  
  def get_composite_rotation_angle_and_axis(angles, axes):
      angle1, axis1 = 0, OUT
      for angle2, axis2 in zip(angles, axes):
          ## Figure out what (angle3, axis3) is the same 
          ## as first applying (angle1, axis1), then (angle2, axis2)
          axis2 = normalize(axis2)
          dot = np.dot(axis2, axis1)
          cross = np.cross(axis2, axis1)
          angle3 = 2*np.arccos(
              np.cos(angle2/2)*np.cos(angle1/2) - \
              np.sin(angle2/2)*np.sin(angle1/2)*dot
          )
          axis3 = (
              np.sin(angle2/2)*np.cos(angle1/2)*axis2 + \
              np.cos(angle2/2)*np.sin(angle1/2)*axis1 + \
              np.sin(angle2/2)*np.sin(angle1/2)*cross
          )
          axis3 = normalize(axis3)
          angle1, axis1 = angle3, axis3
  
      if angle1 > np.pi:
          angle1 -= 2*np.pi
      return angle1, axis1
  
  class ConfettiSpiril(Animation):
      CONFIG = {
          "x_start" : 0,
          "spiril_radius" : 0.5,
          "num_spirils" : 4,
          "run_time" : 10,
          "rate_func" : None,
      }
      def __init__(self, mobject, **kwargs):
          digest_config(self, kwargs)
          mobject.next_to(self.x_start*RIGHT + FRAME_Y_RADIUS*UP, UP)
          self.total_vert_shift = \
              FRAME_HEIGHT + mobject.get_height() + 2*MED_SMALL_BUFF
          
          Animation.__init__(self, mobject, **kwargs)
  
      def interpolate_submobject(self, submobject, starting_submobject, alpha):
          submobject.points = np.array(starting_submobject.points)
  
      def interpolate_mobject(self, alpha):
          Animation.interpolate_mobject(self, alpha)
          angle = alpha*self.num_spirils*2*np.pi
          vert_shift = alpha*self.total_vert_shift
  
          start_center = self.mobject.get_center()
          self.mobject.shift(self.spiril_radius*OUT)
          self.mobject.rotate(angle, axis = UP, about_point = start_center)
          self.mobject.shift(vert_shift*DOWN)
  
  def get_confetti_animations(num_confetti_squares):
      colors = [RED, YELLOW, GREEN, BLUE, PURPLE, RED]
      confetti_squares = [
          Square(
              side_length = 0.2,
              stroke_width = 0,
              fill_opacity = 0.75,
              fill_color = random.choice(colors),
          )
          for x in range(num_confetti_squares)
      ]
      confetti_spirils = [
          ConfettiSpiril(
              square,
              x_start = 2*random.random()*FRAME_X_RADIUS - FRAME_X_RADIUS,
              rate_func = squish_rate_func(lambda t : t, a, a+0.5)
          )
          for a, square in zip(
              np.linspace(0, 0.5, num_confetti_squares),
              confetti_squares
          )
      ]
      return confetti_spirils
  
  class Anniversary(TeacherStudentsScene):
      CONFIG = {
          "num_confetti_squares" : 50,
          "message": "2 year Anniversary!",
      }
      def construct(self):
          self.celebrate()
          self.complain()
  
      def celebrate(self):
          title = TextMobject(self.message)
          title.scale(1.5)
          title.to_edge(UP)
  
          first_video = Rectangle(
              height = 2, width = 2*(16.0/9),
              stroke_color = WHITE,
              fill_color = "#111111",
              fill_opacity = 0.75,
          )
          first_video.next_to(self.get_teacher(), UP+LEFT)
          first_video.shift(RIGHT)
          formula = TexMobject("e^{\\pi i} = -1")
          formula.move_to(first_video)
          first_video.add(formula)
          first_video.fade(1)
  
          hats = self.get_party_hats()
          confetti_spirils = get_confetti_animations(
              self.num_confetti_squares
          )
          self.play(
              Write(title, run_time = 2),
              *[
                  ApplyMethod(pi.change_mode, "hooray")
                  for pi in self.get_pi_creatures()
              ]
          )
          self.play(
              DrawBorderThenFill(
                  hats,
                  lag_ratio = 0.5,
                  rate_func=linear,
                  run_time = 2,
              ),
              *confetti_spirils + [
                  Succession(
                      Animation(pi, run_time = 2),
                      ApplyMethod(pi.look, UP+LEFT),
                      ApplyMethod(pi.look, UP+RIGHT),
                      Animation(pi),
                      ApplyMethod(pi.look_at, first_video),
                      rate_func=linear
                  )
                  for pi in self.get_students()
              ] + [
                  Succession(
                      Animation(self.get_teacher(), run_time = 2),
                      Blink(self.get_teacher()),
                      Animation(self.get_teacher(), run_time = 2),
                      ApplyMethod(self.get_teacher().change_mode, "raise_right_hand"),
                      rate_func=linear
                  ),
                  DrawBorderThenFill(
                      first_video, 
                      run_time = 10,
                      rate_func = squish_rate_func(smooth, 0.5, 0.7)
                  )
              ]
          )
          self.change_student_modes(*["confused"]*3)
  
      def complain(self):
          self.student_says(
              "Why were you \\\\ talking so fast?",
              student_index = 0,
              target_mode = "sassy",
          )
          self.change_student_modes(*["sassy"]*3)
          self.play(self.get_teacher().change_mode, "shruggie")
          self.wait(2)
  
      def get_party_hats(self):
          hats = VGroup(*[
              PartyHat(
                  pi_creature = pi,
                  height = 0.5*pi.get_height()
              )
              for pi in self.get_pi_creatures()
          ])
          max_angle = np.pi/6
          for hat in hats:
              hat.rotate(
                  random.random()*2*max_angle - max_angle,
                  about_point = hat.get_bottom()
              )
          return hats
  
  class HomomophismPreview(Scene):
      def construct(self):
          raise Exception("Meant as a place holder, not to be excecuted")
  
  class WatchingScreen(PiCreatureScene):
      CONFIG = {
          "screen_height" : 5.5
      }
      def create_pi_creatures(self):
          randy = Randolph().to_corner(DOWN+LEFT)
          return VGroup(randy)
  
      def construct(self):
          screen = Rectangle(height = 9, width = 16)
          screen.set_height(self.screen_height)
          screen.to_corner(UP+RIGHT)
  
          self.add(screen)
          for mode in "erm", "pondering", "confused":
              self.wait()
              self.change_mode(mode)
              self.play(Animation(screen))
              self.wait()
  
  class LetsStudyTheBasics(TeacherStudentsScene):
      def construct(self):
          self.teacher_says("Let's learn some \\\\ group theory!")
          self.change_student_modes(*["hooray"]*3)
          self.wait(2)
  
  class JustGiveMeAQuickExplanation(TeacherStudentsScene):
      def construct(self):
          self.student_says(
              "I ain't got \\\\ time for this!",
              target_mode = "angry",
          )
          self.play(*it.chain(*[
              [
                  pi.change_mode, "hesitant", 
                  pi.look_at, self.get_students()[1].eyes
              ]
              for pi in self.get_students()[::2]
          ]))
          self.wait(2)
  
  class QuickExplanation(ComplexTransformationScene):
      CONFIG = {
          "plane_config" : {
              "x_line_frequency" : 1,
              "y_line_frequency" : 1,
              "secondary_line_ratio" : 1,
              "space_unit_to_x_unit" : 1.5,
              "space_unit_to_y_unit" : 1.5,
          },
          "background_fade_factor" : 0.2,
          "background_label_scale_val" : 0.7,
          "velocity_color" : RED,
          "position_color" : YELLOW,
      }
      def construct(self):
          # self.add_transformable_plane()
          self.add_equation()
          self.add_explanation()
          self.add_vectors()
  
      def add_equation(self):
          equation = TexMobject(
              "\\frac{d(e^{it})}{dt}",
              "=",
              "i", "e^{it}"
          )
          equation[0].set_color(self.velocity_color)
          equation[-1].set_color(self.position_color)
          equation.add_background_rectangle()        
          brace = Brace(equation, UP)
          equation.add(brace)
          brace_text = TextMobject(
              "Velocity vector", "is a", 
              "$90^\\circ$ \\\\ rotation", 
              "of", "position vector"
          )
          brace_text[0].set_color(self.velocity_color)
          brace_text[-1].set_color(self.position_color)
          brace_text.add_background_rectangle()
          brace_text.scale(0.8)
          brace_text.to_corner(UP+LEFT, buff = MED_SMALL_BUFF)
          equation.next_to(brace_text, DOWN)
  
          self.add_foreground_mobjects(brace_text, equation)
          self.brace_text = brace_text
  
      def add_explanation(self):
          words = TextMobject("""
              Only a walk around the unit
              circle at rate 1 satisfies both
              this property and e^0 = 1.
          """)
          words.scale(0.8)
          words.add_background_rectangle()
          words.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF)
          arrow = Arrow(RIGHT, 1.5*LEFT, color = WHITE)
          arrow.to_edge(UP)
  
          self.add(words, arrow)
  
      def add_vectors(self):
          right = self.z_to_point(1)
          s_vector = Arrow(
              ORIGIN, right,
              tip_length = 0.2,
              buff = 0,            
              color = self.position_color,
          )
  
          v_vector = s_vector.copy().rotate(np.pi/2)
          v_vector.set_color(self.velocity_color)
          circle = Circle(
              radius = self.z_to_point(1)[0],
              color = self.position_color
          )
  
          self.wait(2)
          self.play(ShowCreation(s_vector))
          self.play(ReplacementTransform(
              s_vector.copy(), v_vector, path_arc = np.pi/2
          ))
          self.wait()
          self.play(v_vector.shift, right)
          self.wait()
          self.vectors = VGroup(s_vector, v_vector)
  
          kwargs = {
              "rate_func" : None,
              "run_time" : 5,
          }
          rotation = Rotating(self.vectors, about_point = ORIGIN, **kwargs)
          self.play(
              ShowCreation(circle, **kwargs),
              rotation            
          )
          self.play(rotation)
          self.play(rotation)
  
  class SymmetriesOfSquare(ThreeDScene):
      CONFIG = {
          "square_config" : {
              "side_length" : 2,
              "stroke_width" : 0,
              "fill_color" : BLUE,
              "fill_opacity" : 0.75,
          },
          "dashed_line_config" : {},
      }
      def construct(self):
          self.add_title()
          self.ask_about_square_symmetry()
          self.talk_through_90_degree_rotation()
          self.talk_through_vertical_flip()
          self.confused_by_lack_of_labels()
          self.add_labels()
          self.show_full_group()
          self.show_top_actions()
          self.show_bottom_actions()
          self.name_dihedral_group()
  
      def add_title(self):
          title = TextMobject("Groups", "$\\leftrightarrow$", "Symmetry")
          title.to_edge(UP)
  
          for index in 0, 2:
              self.play(Write(title[index], run_time = 1))
          self.play(GrowFromCenter(title[1]))
          self.wait()
  
          self.title = title
  
      def ask_about_square_symmetry(self):
          brace = Brace(self.title[-1])
          q_marks = brace.get_text("???")
  
          self.square = Square(**self.square_config)
  
          self.play(DrawBorderThenFill(self.square))
          self.play(GrowFromCenter(brace), Write(q_marks))
          self.rotate_square()
          self.wait()
          for axis in UP, UP+RIGHT:
              self.flip_square(axis)
              self.wait()
          self.rotate_square(-np.pi)
          self.wait()
          self.play(*list(map(FadeOut, [brace, q_marks])))
  
      def talk_through_90_degree_rotation(self):
          arcs = self.get_rotation_arcs(self.square, np.pi/2)
  
          self.play(*list(map(ShowCreation, arcs)))
          self.wait()
          self.rotate_square(np.pi/2, run_time = 2)
          self.wait()
          self.play(FadeOut(arcs))
          self.wait()
  
      def talk_through_vertical_flip(self):
          self.flip_square(UP, run_time = 2)
          self.wait()
  
      def confused_by_lack_of_labels(self):
          randy = Randolph(mode = "confused")
          randy.next_to(self.square, LEFT, buff = LARGE_BUFF)
          randy.to_edge(DOWN)
          self.play(FadeIn(randy))
          for axis in OUT, RIGHT, UP:
              self.rotate_square(
                  angle = np.pi, axis = axis,
                  added_anims = [randy.look_at, self.square.points[0]]
              )
          self.play(Blink(randy))
          self.wait()
  
          self.randy = randy
  
      def add_labels(self):
          self.add_randy_to_square(self.square)
  
          self.play(
              FadeIn(self.square.randy),
              self.randy.change_mode, "happy",
              self.randy.look_at, self.square.randy.eyes
          )
          self.play(Blink(self.randy))
          self.play(FadeOut(self.randy))
  
          self.wait()
  
      def show_full_group(self):
          new_title = TextMobject("Group", "of", "symmetries")
          new_title.move_to(self.title)
  
          all_squares = VGroup(*[
              self.square.copy().scale(0.5)
              for x in range(8)
          ])
          all_squares.arrange(RIGHT, buff = LARGE_BUFF)
  
          top_squares = VGroup(*all_squares[:4])
          bottom_squares = VGroup(*all_squares[4:])
          bottom_squares.next_to(top_squares, DOWN, buff = LARGE_BUFF)
  
          all_squares.set_width(FRAME_WIDTH-2*LARGE_BUFF)
          all_squares.center()
          all_squares.to_edge(DOWN, buff = LARGE_BUFF)
  
          self.play(ReplacementTransform(self.square, all_squares[0]))
          self.play(ReplacementTransform(self.title, new_title))
          self.title = new_title
          self.play(*[
              ApplyMethod(mob.set_color, GREY)
              for mob in self.title[1:]
          ])
  
          for square, angle in zip(all_squares[1:4], [np.pi/2, np.pi, -np.pi/2]):
              arcs = self.get_rotation_arcs(square, angle, MED_SMALL_BUFF)
              self.play(*list(map(FadeIn, [square, arcs])))
              square.rotation_kwargs = {"angle" : angle}
              self.rotate_square(square = square, **square.rotation_kwargs)
              square.add(arcs)
  
          for square, axis in zip(bottom_squares, [RIGHT, RIGHT+UP, UP, UP+LEFT]):
              axis_line = self.get_axis_line(square, axis)
              self.play(FadeIn(square))
              self.play(ShowCreation(axis_line))
              square.rotation_kwargs = {"angle" : np.pi, "axis" : axis}
              self.rotate_square(square = square, **square.rotation_kwargs)
              square.add(axis_line)
          self.wait()
  
          self.all_squares = all_squares
  
      def show_top_actions(self):
          all_squares = self.all_squares
  
          self.play(Indicate(all_squares[0]))
          self.wait()
  
          self.play(*[
              Rotate(
                  square,
                  rate_func = lambda t : -there_and_back(t),
                  run_time = 3,
                  about_point = square.get_center(),
                  **square.rotation_kwargs
              )
              for square in all_squares[1:4]
          ])
          self.wait()
  
      def show_bottom_actions(self):
          for square in self.all_squares[4:]:
              self.rotate_square(
                  square = square,
                  rate_func = there_and_back,
                  run_time = 2,
                  **square.rotation_kwargs
              )
          self.wait()
  
      def name_dihedral_group(self):
          new_title = TextMobject(
              "``Dihedral group'' of order 8"
          )
          new_title.to_edge(UP)
  
          self.play(FadeOut(self.title))
          self.play(FadeIn(new_title))
          self.wait()
  
      ##########
  
      def rotate_square(
          self, 
          angle = np.pi/2, 
          axis = OUT, 
          square = None, 
          show_axis = False,
          added_anims = None,
          **kwargs
          ):
          if square is None:
              assert hasattr(self, "square")
              square = self.square
          added_anims = added_anims or []
          rotation = Rotate(
              square, 
              angle = angle, 
              axis = axis, 
              about_point = square.get_center(),
              **kwargs
          )
          if hasattr(square, "labels"):
              for label in rotation.target_mobject.labels:
                  label.rotate_in_place(-angle, axis)
  
          if show_axis:
              axis_line = self.get_axis_line(square, axis)
              self.play(
                  ShowCreation(axis_line),
                  Animation(square)
              )
          self.play(rotation, *added_anims)
          if show_axis:
              self.play(
                  FadeOut(axis_line),
                  Animation(square)
              )
  
      def flip_square(self, axis = UP, **kwargs):
          self.rotate_square(
              angle = np.pi, 
              axis = axis,
              show_axis = True,
              **kwargs
          )
  
      def get_rotation_arcs(self, square, angle, angle_buff = SMALL_BUFF):
          square_radius = get_norm(
              square.points[0] - square.get_center()
          )
          arc = Arc(
              radius = square_radius + SMALL_BUFF,
              start_angle = np.pi/4 + np.sign(angle)*angle_buff,
              angle = angle - np.sign(angle)*2*angle_buff,
              color = YELLOW
          )
          arc.add_tip()
          if abs(angle) < 3*np.pi/4:
              angle_multiple_range = list(range(1, 4))
          else:
              angle_multiple_range = [2]
          arcs = VGroup(arc, *[
              arc.copy().rotate(i*np.pi/2)
              for i in angle_multiple_range
          ])
          arcs.move_to(square[0])
  
          return arcs
  
      def get_axis_line(self, square, axis):
          axis_line = DashedLine(2*axis, -2*axis, **self.dashed_line_config)
          axis_line.replace(square, dim_to_match = np.argmax(np.abs(axis)))
          axis_line.scale_in_place(1.2)
          return axis_line
  
      def add_labels_and_dots(self, square):
          labels = VGroup()
          dots = VGroup()
          for tex, vertex in zip("ABCD", square.get_anchors()):
              label = TexMobject(tex)
              label.add_background_rectangle()
              label.next_to(vertex, vertex-square.get_center(), SMALL_BUFF)
              labels.add(label)
              dot = Dot(vertex, color = WHITE)
              dots.add(dot)
          square.add(labels, dots)
          square.labels = labels
          square.dots = dots
  
      def add_randy_to_square(self, square, mode = "pondering"):
          randy = Randolph(mode = mode)
          randy.set_height(0.75*square.get_height())
          randy.move_to(square)
          square.add(randy)
          square.randy = randy
  
  class ManyGroupsAreInfinite(TeacherStudentsScene):
      def construct(self):
          self.teacher_says("Many groups are infinite")
          self.change_student_modes(*["pondering"]*3)
          self.wait(2)
  
  class CircleSymmetries(Scene):
      CONFIG = {
          "circle_radius" : 2,
      }
      def construct(self):
          self.add_circle_and_title()        
          self.show_range_of_angles()
          self.associate_rotations_with_points()
  
      def add_circle_and_title(self):
          title = TextMobject("Group of rotations")
          title.to_edge(UP)
  
          circle = self.get_circle()
  
          self.play(Write(title), ShowCreation(circle, run_time = 2))
          self.wait()
          angles = [
              np.pi/2, -np.pi/3, 5*np.pi/6, 
              3*np.pi/2 + 0.1
          ]
          angles.append(-sum(angles))
          for angle in angles:
              self.play(Rotate(circle, angle = angle))
              self.wait()
  
          self.circle = circle
  
      def show_range_of_angles(self):
          self.add_radial_line()
          arc_circle = self.get_arc_circle()
  
          theta = TexMobject("\\theta = ")
          theta_value = DecimalNumber(0.00)
          theta_value.next_to(theta, RIGHT)
          theta_group = VGroup(theta, theta_value)
          theta_group.next_to(arc_circle, UP)
          def theta_value_update(theta_value, alpha):
              new_theta_value = DecimalNumber(alpha*2*np.pi)
              new_theta_value.set_height(theta.get_height())
              new_theta_value.next_to(theta, RIGHT)
              Transform(theta_value, new_theta_value).update(1)
              return new_theta_value
  
  
          self.play(FadeIn(theta_group))
          for rate_func in smooth, lambda t : smooth(1-t):
              self.play(
                  Rotate(self.circle, 2*np.pi-0.001),
                  ShowCreation(arc_circle),
                  UpdateFromAlphaFunc(theta_value, theta_value_update),
                  run_time = 7,
                  rate_func = rate_func
              )
              self.wait()
          self.play(FadeOut(theta_group))
          self.wait()
  
      def associate_rotations_with_points(self):
          zero_dot = Dot(self.circle.point_from_proportion(0))
          zero_dot.set_color(RED)
          zero_arrow = Arrow(UP+RIGHT, ORIGIN)
          zero_arrow.set_color(zero_dot.get_color())
          zero_arrow.next_to(zero_dot, UP+RIGHT, buff = SMALL_BUFF)
  
          self.play(
              ShowCreation(zero_arrow),
              DrawBorderThenFill(zero_dot)
          )
          self.circle.add(zero_dot)
          self.wait()
  
          for alpha in 0.2, 0.6, 0.4, 0.8:
              point = self.circle.point_from_proportion(alpha)
              dot = Dot(point, color = YELLOW)
              vect = np.sign(point)
              arrow = Arrow(vect, ORIGIN)
              arrow.next_to(dot, vect, buff = SMALL_BUFF)
              arrow.set_color(dot.get_color())
              angle = alpha*2*np.pi
  
              self.play(
                  ShowCreation(arrow),
                  DrawBorderThenFill(dot)
              )
              self.play(
                  Rotate(self.circle, angle, run_time = 2),
                  Animation(dot)
              )
              self.wait()            
              self.play(
                  Rotate(self.circle, -angle, run_time = 2),
                  FadeOut(dot),
                  FadeOut(arrow),
              )
              self.wait()
  
      ####
  
      def get_circle(self):
          circle = Circle(color = MAROON_B, radius = self.circle_radius)
          circle.ticks = VGroup()
          for alpha in np.arange(0, 1, 1./8):
              point = circle.point_from_proportion(alpha)
              tick = Line((1 - 0.05)*point, (1 + 0.05)*point)
              circle.ticks.add(tick)
          circle.add(circle.ticks)
          return circle
  
      def add_radial_line(self):
          radius = Line(
              self.circle.get_center(), 
              self.circle.point_from_proportion(0)
          )
          static_radius = radius.copy().set_color(GREY)
  
          self.play(ShowCreation(radius))
          self.add(static_radius, radius)
          self.circle.radius = radius
          self.circle.static_radius = static_radius
          self.circle.add(radius)
  
      def get_arc_circle(self):
          arc_radius = self.circle_radius/5.0
          arc_circle = Circle(
              radius = arc_radius,
              color = WHITE
          )
          return arc_circle
  
  class GroupOfCubeSymmetries(ThreeDScene):
      CONFIG = {
          "cube_opacity" : 0.5,
          "cube_colors" : [BLUE],
          "put_randy_on_cube" : True,
      }
      def construct(self):
          title = TextMobject("Group of cube symmetries")
          title.to_edge(UP)
          self.add(title)
  
          cube = self.get_cube()
  
          face_centers = [face.get_center() for face in cube[0:7:2]]
          angle_axis_pairs = list(zip(3*[np.pi/2], face_centers))
          for i in range(3):
              ones = np.ones(3)
              ones[i] = -1
              axis = np.dot(ones, face_centers)
              angle_axis_pairs.append((2*np.pi/3, axis))
  
          for angle, axis in angle_axis_pairs:
              self.play(Rotate(
                  cube, angle = angle, axis = axis,
                  run_time = 2
              ))
              self.wait()
  
      def get_cube(self):
          cube = Cube(fill_opacity = self.cube_opacity)
          cube.set_color_by_gradient(*self.cube_colors)
          if self.put_randy_on_cube:
              randy = Randolph(mode = "pondering")
              # randy.pupils.shift(0.01*OUT)
              # randy.add(randy.pupils.copy().shift(0.02*IN))
              # for submob in randy.get_family():
              #     submob.part_of_three_d_mobject = True
              randy.scale(0.5)
              face = cube[1]
              randy.move_to(face)
              face.add(randy)
          pose_matrix = self.get_pose_matrix()
          cube.apply_function(
              lambda p : np.dot(p, pose_matrix.T),
              maintain_smoothness = False
          )
          return cube
  
      def get_pose_matrix(self):
          return np.dot(
              rotation_matrix(np.pi/8, UP),
              rotation_matrix(np.pi/24, RIGHT)
          )
  
  class HowDoSymmetriesPlayWithEachOther(TeacherStudentsScene):
      def construct(self):
          self.teacher_says(
              "How do symmetries \\\\ play with each other?",
              target_mode = "hesitant",
          )
          self.change_student_modes("pondering", "maybe", "confused")
          self.wait(2)
  
  class AddSquareSymmetries(SymmetriesOfSquare):
      def construct(self):
          square = Square(**self.square_config)
          square.flip(RIGHT)
          square.shift(DOWN)
          self.add_randy_to_square(square, mode = "shruggie")
          alt_square = square.copy()
          equals = TexMobject("=")
          equals.move_to(square)
  
          equation_square = Square(**self.square_config)
          equation = VGroup(
              equation_square, TexMobject("+"), 
              equation_square.copy(), TexMobject("="),
              equation_square.copy(),
          )
          equation[0].add(self.get_rotation_arcs(
              equation[0], np.pi/2,
          ))
          equation[2].add(self.get_axis_line(equation[4], UP))
          equation[4].add(self.get_axis_line(equation[4], UP+RIGHT))
          for mob in equation[::2]:
              mob.scale(0.5)
          equation.arrange(RIGHT)
          equation.to_edge(UP)
  
          arcs = self.get_rotation_arcs(square, np.pi/2)
  
          self.add(square)
          self.play(FadeIn(arcs))
          self.rotate_square(
              square = square, angle = np.pi/2,
              added_anims = list(map(FadeIn, equation[:2]))
          )
          self.wait()
          self.play(FadeOut(arcs))
          self.flip_square(
              square = square, axis = UP,
              added_anims = list(map(FadeIn, equation[2:4]))
          )
          self.wait()
          alt_square.next_to(equals, RIGHT, buff = LARGE_BUFF)
          alt_square.save_state()
          alt_square.move_to(square)
          alt_square.set_fill(opacity = 0)
          self.play(
              square.next_to, equals, LEFT, LARGE_BUFF,
              alt_square.restore,
              Write(equals)
          )
          self.flip_square(
              square = alt_square, axis = UP+RIGHT,
              added_anims = list(map(FadeIn, equation[4:])),
          )
          self.wait(2)
  
          ## Reiterate composition
          self.rotate_square(square = square, angle = np.pi/2)
          self.flip_square(square = square, axis = UP)
          self.wait()
          self.flip_square(square = alt_square, axis = UP+RIGHT)
          self.wait()
  
  class AddCircleSymmetries(CircleSymmetries):
      def construct(self):
          circle = self.circle = self.get_circle()
          arc_circle = self.get_arc_circle()
          angles = [3*np.pi/2, 2*np.pi/3, np.pi/6]
          arcs = [
              arc_circle.copy().scale(scalar)
              for scalar in [1, 1.2, 1.4]
          ]
  
          equation = TexMobject(
              "270^\\circ", "+", "120^\\circ", "=", "30^\\circ",
          )
          equation.to_edge(UP)
  
          colors = [BLUE, YELLOW, GREEN]
          for color, arc, term in zip(colors, arcs, equation[::2]):
              arc.set_color(color)
              term.set_color(color)
  
          self.play(FadeIn(circle))
          self.add_radial_line()
          alt_radius = circle.radius.copy()
          alt_radius.set_color(GREY)
          alt_circle = circle.copy()
          equals = TexMobject("=")
          equals.move_to(circle)
  
          def rotate(circle, angle, arc, terms):
              self.play(
                  Rotate(circle, angle, in_place = True),
                  ShowCreation(
                      arc,
                      rate_func = lambda t : (angle/(2*np.pi))*smooth(t)
                  ),
                  Write(VGroup(*terms)),
                  run_time = 2,
              )
  
          rotate(circle, angles[0], arcs[0], equation[:2])
          self.wait()
          circle.add(alt_radius)
          rotate(circle, angles[1], arcs[1], equation[2:4])
          self.play(FadeOut(alt_radius))
          circle.remove(alt_radius)
          self.wait()
  
          circle.add(circle.static_radius)
          circle.add(*arcs[:2])
  
          alt_static_radius = circle.static_radius.copy()
          alt_circle.add(alt_static_radius)
          alt_circle.next_to(equals, RIGHT, buff = LARGE_BUFF)
          alt_circle.save_state()
          alt_circle.move_to(circle)
          alt_circle.set_stroke(width = 0)
          self.play(
              circle.next_to, equals, LEFT, LARGE_BUFF,
              alt_circle.restore,
              Write(equals)
          )
          arcs[2].shift(alt_circle.get_center())
          alt_circle.remove(alt_static_radius)
          self.wait()
          rotate(alt_circle, angles[2], arcs[2], equation[4:])
          self.wait()
          self.play(
              Rotate(arcs[1], angles[0], about_point = circle.get_center())
          )
          self.wait(2)
          for term, arc in zip(equation[::2], arcs):
              self.play(*[
                  ApplyMethod(mob.scale_in_place, 1.2, rate_func = there_and_back)
                  for mob in (term, arc)
              ])
              self.wait()
  
  class AddCubeSymmetries(GroupOfCubeSymmetries):
      CONFIG = {
          "angle_axis_pairs" : [
              (np.pi/2, RIGHT),
              (np.pi/2, UP),
          ],
          "cube_opacity" : 0.5,
          "cube_colors" : [BLUE],
      }
      def construct(self):
          angle_axis_pairs = list(self.angle_axis_pairs)
          angle_axis_pairs.append(
              self.get_composition_angle_and_axis()
          )
          self.pose_matrix = self.get_pose_matrix()
          cube = self.get_cube()
  
          equation = cube1, plus, cube2, equals, cube3 = VGroup(
              cube, TexMobject("+"), 
              cube.copy(), TexMobject("="),
              cube.copy()
          )
          equation.arrange(RIGHT, buff = MED_LARGE_BUFF)
          equation.center()
  
          self.add(cube1)
          self.rotate_cube(cube1, *angle_axis_pairs[0])
          cube_copy = cube1.copy()
          cube_copy.set_fill(opacity = 0)
          self.play(
              cube_copy.move_to, cube2,
              cube_copy.set_fill, None, self.cube_opacity,
              Write(plus)
          )
          self.rotate_cube(cube_copy, *angle_axis_pairs[1])
          self.play(Write(equals))
          self.play(DrawBorderThenFill(cube3, run_time = 1))
          self.rotate_cube(cube3, *angle_axis_pairs[2])
          self.wait(2)
  
          times = TexMobject("\\times")
          times.scale(1.5)
          times.move_to(plus)
          times.set_color(RED)
          self.wait()
          self.play(ReplacementTransform(plus, times))
          self.play(Indicate(times))
          self.wait()
          for cube, (angle, axis) in zip([cube1, cube_copy, cube3], angle_axis_pairs):
              self.rotate_cube(
                  cube, -angle, axis, add_arrows = False,
                  rate_func = there_and_back,
                  run_time = 1.5
              )
          self.wait()
  
      def rotate_cube(self, cube, angle, axis, add_arrows = True, **kwargs):
          axis = np.dot(axis, self.pose_matrix.T)
          anims = []
          if add_arrows:
              arrows = VGroup(*[
                  Arc(
                      start_angle = np.pi/12+a, angle = 5*np.pi/6,
                      color = YELLOW
                  ).add_tip()
                  for a in (0, np.pi)
              ])
              arrows.set_height(1.5*cube.get_height())
              z_to_axis = z_to_vector(axis)
              arrows.apply_function(
                  lambda p : np.dot(p, z_to_axis.T),
                  maintain_smoothness = False
              )
              arrows.move_to(cube)
              arrows.shift(-axis*cube.get_height()/2/get_norm(axis))
              anims += list(map(ShowCreation, arrows))
          anims.append(
              Rotate(
                  cube, axis = axis, angle = angle, in_place = True,
                  **kwargs
              )
          )
          self.play(*anims, run_time = 1.5)
  
      def get_composition_angle_and_axis(self):
          return get_composite_rotation_angle_and_axis(
              *list(zip(*self.angle_axis_pairs))
          )
  
  class DihedralGroupStructure(SymmetriesOfSquare):
      CONFIG = {
          "dashed_line_config" : {
              "dash_length" : 0.1
          },
          "filed_sum_scale_factor" : 0.4,
          "num_rows" : 5,
      }
      def construct(self):
          angle_axis_pairs = [
              (np.pi/2, OUT),
              (np.pi, OUT),
              (-np.pi/2, OUT),
              # (np.pi, RIGHT),
              # (np.pi, UP+RIGHT),
              (np.pi, UP),
              (np.pi, UP+LEFT),
          ]
          pair_pairs = list(it.product(*[angle_axis_pairs]*2))
          random.shuffle(pair_pairs)
          for pair_pair in pair_pairs[:4]:
              sum_expression = self.demonstrate_sum(pair_pair)
              self.file_away_sum(sum_expression)
          for pair_pair in pair_pairs[4:]:
              should_skip_animations = self.skip_animations
              self.skip_animations = True
              sum_expression = self.demonstrate_sum(pair_pair)
              self.file_away_sum(sum_expression)
              self.skip_animations = should_skip_animations
              self.play(FadeIn(sum_expression))
          self.wait(3)
  
  
      def demonstrate_sum(self, angle_axis_pairs):
          angle_axis_pairs = list(angle_axis_pairs) + [
              get_composite_rotation_angle_and_axis(
                  *list(zip(*angle_axis_pairs))
              )
          ]
  
          prototype_square = Square(**self.square_config)
          prototype_square.flip(RIGHT)
          self.add_randy_to_square(prototype_square)
  
          # self.add_labels_and_dots(prototype_square)
          prototype_square.scale(0.7)
          expression = s1, plus, s2, equals, s3 = VGroup(
              prototype_square, TexMobject("+").scale(2), 
              prototype_square.copy(), TexMobject("=").scale(2),
              prototype_square.copy()
          )
  
          final_expression = VGroup()
          for square, (angle, axis) in zip([s1, s2, s3], angle_axis_pairs):
              if np.cos(angle) > 0.5:
                  square.action_illustration = VectorizedPoint()
              elif np.argmax(np.abs(axis)) == 2: ##Axis is in z direction
                  square.action_illustration = self.get_rotation_arcs(
                      square, angle
                  )
              else:
                  square.action_illustration = self.get_axis_line(
                      square, axis
                  )
              square.add(square.action_illustration)
              final_expression.add(square.action_illustration)
              square.rotation_kwargs = {
                  "square" : square,
                  "angle" : angle,
                  "axis" : axis,
              }
          expression.arrange()
          expression.set_width(FRAME_X_RADIUS+1)
          expression.to_edge(RIGHT, buff = SMALL_BUFF)
          for square in s1, s2, s3:
              square.remove(square.action_illustration)
  
          self.play(FadeIn(s1))
          self.play(*list(map(ShowCreation, s1.action_illustration)))
          self.rotate_square(**s1.rotation_kwargs)
  
          s1_copy = s1.copy()
          self.play(
              # FadeIn(s2),
              s1_copy.move_to, s2,
              Write(plus)
          )
          Transform(s2, s1_copy).update(1)
          self.remove(s1_copy)
          self.add(s2)
          self.play(*list(map(ShowCreation, s2.action_illustration)))
          self.rotate_square(**s2.rotation_kwargs)
  
          self.play(
              Write(equals),
              FadeIn(s3)
          )
          self.play(*list(map(ShowCreation, s3.action_illustration)))
          self.rotate_square(**s3.rotation_kwargs)
          self.wait()
          final_expression.add(*expression)
  
          return final_expression
  
      def file_away_sum(self, sum_expression):
          if not hasattr(self, "num_sum_expressions"):
              self.num_sum_expressions = 0
          target = sum_expression.copy()
          target.scale(self.filed_sum_scale_factor)
          y_index = self.num_sum_expressions%self.num_rows
          y_prop = float(y_index)/(self.num_rows-1)
          y = interpolate(FRAME_Y_RADIUS-LARGE_BUFF, -FRAME_Y_RADIUS+LARGE_BUFF, y_prop)
          x_index = self.num_sum_expressions//self.num_rows
          x_spacing = FRAME_WIDTH/3
          x = (x_index-1)*x_spacing
  
          target.move_to(x*RIGHT + y*UP)
  
          self.play(Transform(sum_expression, target))
          self.wait()
  
          self.num_sum_expressions += 1
          self.last_sum_expression = sum_expression
  
  class ThisIsAVeryGeneralIdea(Scene):
      def construct(self):
          groups = TextMobject("Groups")
          groups.to_edge(UP)
          groups.set_color(BLUE)
  
          examples = VGroup(*list(map(TextMobject, [
              "Square matrices \\\\ \\small (Where $\\det(M) \\ne 0$)",
              "Molecular \\\\ symmetry",
              "Cryptography",
              "Numbers",
          ])))
          numbers = examples[-1]
          examples.arrange(buff = LARGE_BUFF)
          examples.set_width(FRAME_WIDTH-1)
          examples.move_to(UP)
  
          lines = VGroup(*[
              Line(groups.get_bottom(), ex.get_top(), buff = MED_SMALL_BUFF)
              for ex in examples
          ])
          lines.set_color(groups.get_color())
  
          self.add(groups)
  
          for example, line in zip(examples, lines):
              self.play(
                  ShowCreation(line),
                  Write(example, run_time = 2)
              )
          self.wait()
          self.play(
              VGroup(*examples[:-1]).fade, 0.7,
              VGroup(*lines[:-1]).fade, 0.7,
          )
  
          self.play(
              numbers.scale, 1.2, numbers.get_corner(UP+RIGHT),
          )
          self.wait(2)
  
          sub_categories = VGroup(*list(map(TextMobject, [
              "Numbers \\\\ (Additive)",
              "Numbers \\\\ (Multiplicative)",
          ])))
          sub_categories.arrange(RIGHT, buff = MED_LARGE_BUFF)
          sub_categories.next_to(numbers, DOWN, 1.5*LARGE_BUFF)
          sub_categories.to_edge(RIGHT)
          sub_categories[0].set_color(ADDER_COLOR)
          sub_categories[1].set_color(MULTIPLIER_COLOR)
  
          sub_lines = VGroup(*[
              Line(numbers.get_bottom(), sc.get_top(), buff = MED_SMALL_BUFF)
              for sc in sub_categories
          ])
          sub_lines.set_color(numbers.get_color())
  
          self.play(*it.chain(
              list(map(ShowCreation, sub_lines)),
              list(map(Write, sub_categories))
          ))
          self.wait()
  
  class NumbersAsActionsQ(TeacherStudentsScene):
      def construct(self):
          self.student_says(
              "Numbers are actions?",
              target_mode = "confused",
          )
          self.change_student_modes("pondering", "confused", "erm")
          self.play(self.get_teacher().change_mode, "happy")
          self.wait(3)
  
  class AdditiveGroupOfReals(Scene):
      CONFIG = {
          "number_line_center" : UP,
          "shadow_line_center" : DOWN,
          "zero_color" : GREEN_B,
          "x_min" : -FRAME_WIDTH,
          "x_max" : FRAME_WIDTH,
      }
      def construct(self):
          self.add_number_line()
          self.show_example_slides(3, -7)
          self.write_group_of_slides()
          self.show_example_slides(2, 6, -1, -3)
          self.mark_zero()
          self.show_example_slides_labeled(3, -2)
          self.comment_on_zero_as_identity()
          self.show_example_slides_labeled(
              5.5, added_anims = [self.get_write_name_of_group_anim()]
          )
          self.show_example_additions((3, 2), (2, -5), (-4, 4))
  
      def add_number_line(self):
          number_line = NumberLine(
              x_min = self.x_min,
              x_max = self.x_max,
          )
  
          number_line.shift(self.number_line_center)
          shadow_line = NumberLine(color = GREY, stroke_width = 2)
          shadow_line.shift(self.shadow_line_center)
          for line in number_line, shadow_line:
              line.add_numbers()
          shadow_line.numbers.fade(0.25)
          shadow_line.save_state()
          shadow_line.set_color(BLACK)
          shadow_line.move_to(number_line)
  
  
          self.play(*list(map(Write, number_line)), run_time = 1)
          self.play(shadow_line.restore, Animation(number_line))
          self.wait()
  
          self.number_line = number_line
          self.shadow_line = shadow_line
  
      def show_example_slides(self, *nums):
          for num in nums:
              zero_point = self.number_line.number_to_point(0)            
              num_point = self.number_line.number_to_point(num)
              arrow = Arrow(zero_point, num_point, buff = 0)
              arrow.set_color(ADDER_COLOR)
              arrow.shift(MED_LARGE_BUFF*UP)
  
              self.play(ShowCreation(arrow))
              self.play(
                  self.number_line.shift,
                  num_point - zero_point,
                  run_time = 2
              )
              self.play(FadeOut(arrow))
  
      def write_group_of_slides(self):
          title = TextMobject("Group of line symmetries")
          title.to_edge(UP)
          self.play(Write(title))
          self.title = title
  
      def mark_zero(self):
          dot = Dot(
              self.number_line.number_to_point(0),
              color = self.zero_color
          )
          arrow = Arrow(dot, color = self.zero_color)
          words = TextMobject("Follow zero")
          words.next_to(arrow.get_start(), UP)
          words.set_color(self.zero_color)
  
          self.play(
              ShowCreation(arrow),
              DrawBorderThenFill(dot),
              Write(words),
          )
          self.wait()
          self.play(*list(map(FadeOut, [arrow, words])))
  
          self.number_line.add(dot)
  
      def show_example_slides_labeled(self, *nums, **kwargs):
          for num in nums:
              line = DashedLine(
                  self.number_line.number_to_point(num)+MED_LARGE_BUFF*UP,
                  self.shadow_line.number_to_point(num)+MED_LARGE_BUFF*DOWN,
              )
              vect = self.number_line.number_to_point(num) - \
                     self.number_line.number_to_point(0)
              self.play(ShowCreation(line))
              self.wait()
              self.play(self.number_line.shift, vect, run_time = 2)
              self.wait()
              if "added_anims" in kwargs:
                  self.play(*kwargs["added_anims"])
                  self.wait()
              self.play(
                  self.number_line.shift, -vect,
                  FadeOut(line)
              )
  
      def comment_on_zero_as_identity(self):
          line = DashedLine(
              self.number_line.number_to_point(0)+MED_LARGE_BUFF*UP,
              self.shadow_line.number_to_point(0)+MED_LARGE_BUFF*DOWN,
          )
          words = TexMobject("0 \\leftrightarrow \\text{Do nothing}")
          words.shift(line.get_top()+MED_SMALL_BUFF*UP - words[0].get_bottom())
  
          self.play(
              ShowCreation(line),
              Write(words)
          )
          self.wait(2)
          self.play(*list(map(FadeOut, [line, words])))
  
      def get_write_name_of_group_anim(self):
          new_title = TextMobject("Additive group of real numbers")
          VGroup(*new_title[-len("realnumbers"):]).set_color(BLUE)
          VGroup(*new_title[:len("Additive")]).set_color(ADDER_COLOR)
          new_title.to_edge(UP)
          return Transform(self.title, new_title)
  
      def show_example_additions(self, *num_pairs):
          for num_pair in num_pairs:
              num_mobs = VGroup()
              arrows = VGroup()
              self.number_line.save_state()
              for num in num_pair:
                  zero_point, num_point, arrow, num_mob = \
                      self.get_adder_mobs(num)
                  if len(num_mobs) > 0:
                      last_num_mob = num_mobs[0]
                      x = num_mob.get_center()[0]
                      if x < last_num_mob.get_right()[0] and x > last_num_mob.get_left()[0]:
                          num_mob.next_to(last_num_mob, RIGHT)
                  num_mobs.add(num_mob)
                  arrows.add(arrow)
  
                  self.play(
                      ShowCreation(arrow),
                      Write(num_mob, run_time = 1)
                  )
                  self.play(
                      self.number_line.shift, 
                      num_point - zero_point
                  )
                  self.wait()
              #Reset
              self.play(
                  FadeOut(num_mobs),
                  FadeOut(self.number_line)
              )
              ApplyMethod(self.number_line.restore).update(1)
              self.play(FadeIn(self.number_line))
  
              #Sum arrow
              num = sum(num_pair)
              zero_point, sum_point, arrow, sum_mob = \
                  self.get_adder_mobs(sum(num_pair))
              VGroup(arrow, sum_mob).shift(MED_LARGE_BUFF*UP)
              arrows.add(arrow)
              self.play(
                  ShowCreation(arrow),
                  Write(sum_mob, run_time = 1)
              )
              self.wait()
              self.play(
                  self.number_line.shift, 
                  num_point - zero_point,
                  run_time = 2
              )
              self.wait()
              self.play(
                  self.number_line.restore,
                  *list(map(FadeOut, [arrows, sum_mob]))
              )
  
      def get_adder_mobs(self, num):
          zero_point = self.number_line.number_to_point(0)            
          num_point = self.number_line.number_to_point(num)
          arrow = Arrow(zero_point, num_point, buff = 0)
          arrow.set_color(ADDER_COLOR)
          arrow.shift(MED_SMALL_BUFF*UP)
          if num == 0:
              arrow = DashedLine(UP, ORIGIN)
              arrow.move_to(zero_point)
          elif num < 0:
              arrow.set_color(RED)
              arrow.shift(SMALL_BUFF*UP)
          sign = "+" if num >= 0 else ""
          num_mob = TexMobject(sign + str(num))
          num_mob.next_to(arrow, UP)
          num_mob.set_color(arrow.get_color())
          return zero_point, num_point, arrow, num_mob
  
  class AdditiveGroupOfComplexNumbers(ComplexTransformationScene):
      CONFIG = {
          "x_min" : -2*int(FRAME_X_RADIUS),
          "x_max" : 2*int(FRAME_X_RADIUS),
          "y_min" : -FRAME_HEIGHT,
          "y_max" : FRAME_HEIGHT,
          "example_points" : [
              complex(3, 2),
              complex(1, -3),
          ]
      }
      def construct(self):
          self.add_plane()
          self.show_preview_example_slides()
          self.show_vertical_slide()
          self.show_example_point()
          self.show_example_addition()
          self.write_group_name()
          self.show_some_random_slides()
  
      def add_plane(self):
          self.add_transformable_plane(animate = True)
          zero_dot = Dot(
              self.z_to_point(0),
              color = ADDER_COLOR
          )
          self.play(ShowCreation(zero_dot))
          self.plane.add(zero_dot)
          self.plane.zero_dot = zero_dot
          self.wait()
  
      def show_preview_example_slides(self):
          example_vect = 2*UP+RIGHT
          for vect in example_vect, -example_vect:
              self.play(self.plane.shift, vect, run_time = 2)
              self.wait()
  
      def show_vertical_slide(self):
          dots = VGroup(*[
              Dot(self.z_to_point(complex(0, i)))
              for i in range(1, 4)
          ])
          dots.set_color(YELLOW)
          labels = VGroup(*self.imag_labels[-3:])
  
          arrow = Arrow(ORIGIN, dots[-1].get_center(), buff = 0)
          arrow.set_color(ADDER_COLOR)
  
          self.plane.save_state()
          for dot, label in zip(dots, labels):
              self.play(
                  Indicate(label),
                  ShowCreation(dot)
              )
          self.add_foreground_mobjects(dots)
          self.wait()
          Scene.play(self, ShowCreation(arrow))
          self.add_foreground_mobjects(arrow)
          self.play(
              self.plane.shift, dots[-1].get_center(),
              run_time = 2
          )
          self.wait()
          self.play(FadeOut(arrow))
          self.foreground_mobjects.remove(arrow)
          self.play(
              self.plane.shift, 6*DOWN,
              run_time = 3,
          )
          self.wait()
          self.play(self.plane.restore, run_time = 2)
          self.foreground_mobjects.remove(dots)
          self.play(FadeOut(dots))
  
      def show_example_point(self):
          z = self.example_points[0]
          point = self.z_to_point(z)
          dot = Dot(point, color = YELLOW)
          arrow = Vector(point, buff = dot.radius)
          arrow.set_color(dot.get_color())
          label = TexMobject("%d + %di"%(z.real, z.imag))
          label.next_to(point, UP)
          label.set_color(dot.get_color())
          label.add_background_rectangle()
  
          real_arrow = Vector(self.z_to_point(z.real))
          imag_arrow = Vector(self.z_to_point(z - z.real))
          VGroup(real_arrow, imag_arrow).set_color(ADDER_COLOR)
  
          self.play(
              Write(label),
              DrawBorderThenFill(dot)
          )
          self.wait()
          self.play(ShowCreation(arrow))
          self.add_foreground_mobjects(label, dot, arrow)
          self.wait()
          self.slide(z)
          self.wait()
          self.play(FadeOut(self.plane))
          self.plane.restore()
          self.plane.set_stroke(width = 0)
          self.play(self.plane.restore)
          self.play(ShowCreation(real_arrow))
          self.add_foreground_mobjects(real_arrow)
          self.slide(z.real)
          self.wait()
          self.play(ShowCreation(imag_arrow))
          self.wait()
          self.play(imag_arrow.shift, self.z_to_point(z.real))
          self.add_foreground_mobjects(imag_arrow)
          self.slide(z - z.real)
          self.wait()
  
          self.foreground_mobjects.remove(real_arrow)
          self.foreground_mobjects.remove(imag_arrow)
          self.play(*list(map(FadeOut, [real_arrow, imag_arrow, self.plane])))
          self.plane.restore()
          self.plane.set_stroke(width = 0)
          self.play(self.plane.restore)
  
          self.z1 = z
          self.arrow1 = arrow
          self.dot1 = dot
          self.label1 = label
  
      def show_example_addition(self):
          z1 = self.z1
          arrow1 = self.arrow1
          dot1 = self.dot1
          label1 = self.label1
  
          z2 = self.example_points[1]
          point2 = self.z_to_point(z2)
          dot2 = Dot(point2, color = TEAL)
          arrow2 = Vector(
              point2, 
              buff = dot2.radius,
              color = dot2.get_color()
          )
          label2 = TexMobject(
              "%d %di"%(z2.real, z2.imag)
          )
          label2.next_to(point2, UP+RIGHT)
          label2.set_color(dot2.get_color())
          label2.add_background_rectangle()
  
          self.play(ShowCreation(arrow2))
          self.play(
              DrawBorderThenFill(dot2),
              Write(label2)
          )
          self.add_foreground_mobjects(arrow2, dot2, label2)
          self.wait()
  
          self.slide(z1)
          arrow2_copy = arrow2.copy()
          self.play(arrow2_copy.shift, self.z_to_point(z1))
          self.add_foreground_mobjects(arrow2_copy)
          self.slide(z2)
          self.play(FadeOut(arrow2_copy))
          self.foreground_mobjects.remove(arrow2_copy)
          self.wait()
  
          ##Break into components
          real_arrow, imag_arrow = component_arrows = [
              Vector(
                  self.z_to_point(z),
                  color = ADDER_COLOR
              )
              for z in [
                  z1.real+z2.real,
                  complex(0, z1.imag+z2.imag),
              ]
          ]
          imag_arrow.shift(real_arrow.get_end())
          plus = TexMobject("+").next_to(
              real_arrow.get_center(), UP+RIGHT
          )
          plus.add_background_rectangle()
  
          rp1, rp2, ip1, ip2 = label_parts = [
              VGroup(label1[1][0].copy()),
              VGroup(label2[1][0].copy()),
              VGroup(*label1[1][2:]).copy(),
              VGroup(*label2[1][1:]).copy(),
          ]
          for part in label_parts:
              part.generate_target()
  
          rp1.target.next_to(plus, LEFT)
          rp2.target.next_to(plus, RIGHT)
          ip1.target.next_to(imag_arrow.get_center(), RIGHT)
          ip1.target.shift(SMALL_BUFF*DOWN)
          ip2.target.next_to(ip1.target, RIGHT)
  
          real_background_rect = BackgroundRectangle(
              VGroup(rp1.target, rp2.target)
          )
          imag_background_rect = BackgroundRectangle(
              VGroup(ip1.target, ip2.target)
          )
  
          self.play(
              ShowCreation(real_arrow),
              ShowCreation(
                  real_background_rect,
                  rate_func = squish_rate_func(smooth, 0.75, 1),
              ),
              Write(plus),
              *list(map(MoveToTarget, [rp1, rp2]))
          )
          self.wait()
          self.play(
              ShowCreation(imag_arrow),
              ShowCreation(
                  imag_background_rect,
                  rate_func = squish_rate_func(smooth, 0.75, 1),
              ),
              *list(map(MoveToTarget, [ip1, ip2]))
          )
          self.wait(2)
          to_remove = [
              arrow1, dot1, label1,
              arrow2, dot2, label2,
              real_background_rect,
              imag_background_rect,
              plus,
          ] + label_parts + component_arrows
          for mob in to_remove:
              if mob in self.foreground_mobjects:
                  self.foreground_mobjects.remove(mob)
          self.play(*list(map(FadeOut, to_remove)))
          self.play(self.plane.restore, run_time = 2)
          self.wait()
  
      def write_group_name(self):
          title = TextMobject(
              "Additive", "group of", "complex numbers"
          )
          title[0].set_color(ADDER_COLOR)
          title[2].set_color(BLUE)
          title.add_background_rectangle()
          title.to_edge(UP, buff = MED_SMALL_BUFF)
  
          self.play(Write(title))
          self.add_foreground_mobjects(title)
          self.wait()
  
      def show_some_random_slides(self):
          example_slides = [
              complex(3),
              complex(0, 2),
              complex(-4, -1),
              complex(-2, -1),
              complex(4, 2),
          ]
          for z in example_slides:
              self.slide(z)
              self.wait()
  
      #########
  
      def slide(self, z, *added_anims, **kwargs):
          kwargs["run_time"] = kwargs.get("run_time", 2)
          self.play(
              ApplyMethod(
                  self.plane.shift, self.z_to_point(z),
                  **kwargs
              ),
              *added_anims
          )
  
  class SchizophrenicNumbers(Scene):
      def construct(self):
          v_line = DashedLine(
              FRAME_Y_RADIUS*UP,
              FRAME_Y_RADIUS*DOWN
          )
          left_title = TextMobject("Additive group")
          left_title.shift(FRAME_X_RADIUS*LEFT/2)
          right_title = TextMobject("Multiplicative group")
          right_title.shift(FRAME_X_RADIUS*RIGHT/2)
          VGroup(left_title, right_title).to_edge(UP)
          self.add(v_line, left_title, right_title)
  
          numbers = VGroup(
              Randolph(mode = "happy").scale(0.2),
              TexMobject("3").shift(UP+LEFT),
              TexMobject("5.83").shift(UP+RIGHT),
              TexMobject("\\sqrt{2}").shift(DOWN+LEFT),
              TexMobject("2-i").shift(DOWN+RIGHT),
          )
          for number in numbers:
              number.set_color(ADDER_COLOR)
              number.scale(1.5)            
              if isinstance(number, PiCreature):
                  continue
              number.eyes = Eyes(number[0], height = 0.1)
              number.add(number.eyes)
          numbers[3].eyes.next_to(numbers[3][1], UP, buff = 0)
          numbers.shift(FRAME_X_RADIUS*LEFT/2)
  
          self.play(FadeIn(numbers))
          self.blink_numbers(numbers)
          self.wait()
          self.add(numbers.copy())
          for number in numbers:
              number.generate_target()
              number.target.shift(FRAME_X_RADIUS*RIGHT)
              number.target.eyes.save_state()
              number.target.set_color(MULTIPLIER_COLOR)
              number.target.eyes.restore()
          self.play(*[
              MoveToTarget(
                  number, 
                  rate_func = squish_rate_func(
                      smooth, alpha, alpha+0.5
                  ),
                  run_time = 2,
              )
              for number, alpha in zip(numbers, np.linspace(0, 0.5, len(numbers)))
          ])
          self.wait()
          self.blink_numbers(numbers)
          self.wait()
  
      def blink_numbers(self, numbers):
          self.play(*[
              num.eyes.blink_anim(
                  rate_func = squish_rate_func(
                      there_and_back, alpha, alpha+0.2
                  )
              )
              for num, alpha in zip(
                  numbers[1:], 0.8*np.random.random(len(numbers))
              )
          ])
  
  class MultiplicativeGroupOfReals(AdditiveGroupOfReals):
      CONFIG = {
          "number_line_center" : 0.5*UP,
          "shadow_line_center" : 1.5*DOWN,
          "x_min" : -3*FRAME_X_RADIUS,
          "x_max" : 3*FRAME_X_RADIUS,
          "positive_reals_color" : MAROON_B,
      }
      def setup(self):
          self.foreground_mobjects = VGroup()
  
      def construct(self):
          self.add_title()
          self.add_number_line()
          self.introduce_stretch_and_squish()
          self.show_zero_fixed_in_place()
          self.follow_one()
          self.every_positive_number_association()
          self.compose_actions(3, 2)
          self.compose_actions(4, 0.5)
          self.write_group_name()
          self.compose_actions(1.5, 1.5)
  
      def add_title(self):
          self.title = TextMobject("Group of stretching/squishing actions")
          self.title.to_edge(UP)
          self.add(self.title)
  
      def add_number_line(self):
          AdditiveGroupOfReals.add_number_line(self)
          self.zero_point = self.number_line.number_to_point(0)
          self.one = [m for m in self.number_line.numbers if m.get_tex_string() is "1"][0]
          self.one.add_background_rectangle()
          self.one.background_rectangle.scale_in_place(1.3)
          self.number_line.save_state()        
  
      def introduce_stretch_and_squish(self):
          for num in [3, 0.25]:
              self.stretch(num)
              self.wait()
          self.play(self.number_line.restore)
          self.wait()
  
      def show_zero_fixed_in_place(self):
          arrow = Arrow(self.zero_point + UP, self.zero_point, buff = 0)
          arrow.set_color(ADDER_COLOR)
          words = TextMobject("Fix zero")
          words.set_color(ADDER_COLOR)
          words.next_to(arrow, UP)
  
          self.play(
              ShowCreation(arrow),
              Write(words)
          )
          self.foreground_mobjects.add(arrow)
          self.stretch(4)
          self.stretch(0.1)
          self.wait()
          self.play(self.number_line.restore)
          self.play(FadeOut(words))
          self.wait()
  
          self.zero_arrow = arrow
  
      def follow_one(self):
          dot = Dot(self.number_line.number_to_point(1))
          arrow = Arrow(dot.get_center()+UP+RIGHT, dot)
          words = TextMobject("Follow one")
          words.next_to(arrow.get_start(), UP)
          for mob in dot, arrow, words:
              mob.set_color(MULTIPLIER_COLOR)
  
          three_line, half_line = [
              DashedLine(
                  self.number_line.number_to_point(num),
                  self.shadow_line.number_to_point(num)
              )
              for num in (3, 0.5)
          ]
          three_mob = [m for m in self.shadow_line.numbers if m.get_tex_string() == "3"][0]
          half_point = self.shadow_line.number_to_point(0.5)
          half_arrow = Arrow(
              half_point+UP+LEFT, half_point, buff = SMALL_BUFF,
              tip_length = 0.15,
          )
          half_label = TexMobject("1/2")
          half_label.scale(0.7)
          half_label.set_color(MULTIPLIER_COLOR)
          half_label.next_to(half_arrow.get_start(), LEFT, buff = SMALL_BUFF)
          
          self.play(
              ShowCreation(arrow),
              DrawBorderThenFill(dot),
              Write(words)
          )
          self.number_line.add(dot)
          self.number_line.numbers.add(dot)
          self.number_line.save_state()
          self.wait()
          self.play(*list(map(FadeOut, [arrow, words])))
  
          self.stretch(3)
          self.play(
              ShowCreation(three_line),
              Animation(self.one)
          )
          dot_copy = dot.copy()
          self.play(
              dot_copy.move_to, three_line.get_bottom()
          )
          self.play(Indicate(three_mob, run_time = 2))
          self.wait()
          self.play(
              self.number_line.restore,
              *list(map(FadeOut, [three_line, dot_copy]))
          )
          self.wait()
          self.stretch(0.5)
          self.play(
              ShowCreation(half_line),
              Animation(self.one)
          )
          dot_copy = dot.copy()
          self.play(
              dot_copy.move_to, half_line.get_bottom()
          )
          self.play(
              Write(half_label),
              ShowCreation(half_arrow)
          )
          self.wait()
          self.play(
              self.number_line.restore,
              *list(map(FadeOut, [
                  half_label, half_arrow, 
                  half_line, dot_copy
              ]))
          )
          self.wait()
  
          self.one_dot = dot
  
      def every_positive_number_association(self):
          positive_reals_line = Line(
              self.shadow_line.number_to_point(0),
              self.shadow_line.number_to_point(FRAME_X_RADIUS),
              color = self.positive_reals_color
          )
          positive_reals_words = TextMobject("All positive reals")
          positive_reals_words.set_color(self.positive_reals_color)
          positive_reals_words.next_to(positive_reals_line, UP)
          positive_reals_words.add_background_rectangle()
  
          third_line, one_line = [
              DashedLine(
                  self.number_line.number_to_point(num),
                  self.shadow_line.number_to_point(num)
              )
              for num in (0.33, 1)
          ]
  
          self.play(
              self.zero_arrow.shift, 0.5*UP,
              rate_func = there_and_back
          )
          self.wait()
          self.play(
              self.one_dot.shift, 0.25*UP,
              rate_func = wiggle
          )
          self.stretch(3)
          self.stretch(0.33/3, run_time = 3)
          self.wait()
          self.play(ShowCreation(third_line), Animation(self.one))
          self.play(
              ShowCreation(positive_reals_line),
              Write(positive_reals_words),
          )
          self.wait()
          self.play(
              ReplacementTransform(third_line, one_line),
              self.number_line.restore,
              Animation(positive_reals_words),
              run_time = 2
          )
          self.number_line.add_to_back(one_line)
          self.number_line.save_state()
          self.stretch(
              7, run_time = 10, rate_func = there_and_back,
              added_anims = [Animation(positive_reals_words)]
          )
          self.wait()
  
      def compose_actions(self, num1, num2):
          words = VGroup(*[
              TextMobject("(%s by %s)"%(word, str(num)))
              for num in (num1, num2, num1*num2)
              for word in ["Stretch" if num > 1 else "Squish"]
          ])
          words.submobjects.insert(2, TexMobject("="))
          words.arrange(RIGHT)
          top_words = VGroup(*words[:2])
          top_words.set_color(MULTIPLIER_COLOR)
          bottom_words = VGroup(*words[2:])
          bottom_words.next_to(top_words, DOWN)
          words.scale(0.8)
          words.next_to(self.number_line, UP)
          words.to_edge(RIGHT)
  
          for num, word in zip([num1, num2], top_words):
              self.stretch(
                  num, 
                  added_anims = [FadeIn(word)],
                  run_time = 3
              )
              self.wait()
          self.play(Write(bottom_words, run_time = 2))
          self.wait(2)
          self.play(
              ApplyMethod(self.number_line.restore, run_time = 2),
              FadeOut(words),
          )
          self.wait()
  
      def write_group_name(self):
          new_title = TextMobject(
              "Multiplicative group of positive real numbers"
          )
          new_title.to_edge(UP)
          VGroup(
              *new_title[:len("Multiplicative")]
          ).set_color(MULTIPLIER_COLOR)
          VGroup(
              *new_title[-len("positiverealnumbers"):]
          ).set_color(self.positive_reals_color)
  
          self.play(Transform(self.title, new_title))
          self.wait()
  
      ###
  
      def stretch(self, factor, run_time = 2, **kwargs):
          kwargs["run_time"] = run_time
          target = self.number_line.copy()
          target.stretch_about_point(factor, 0, self.zero_point)
          total_factor = (target.number_to_point(1)-self.zero_point)[0]
          for number in target.numbers:
              number.stretch_in_place(1./factor, dim = 0)
              if total_factor < 0.7:
                  number.stretch_in_place(total_factor, dim = 0)
          self.play(
              Transform(self.number_line, target, **kwargs),
              *kwargs.get("added_anims", [])
          )
  
      def play(self, *anims, **kwargs):
          anims = list(anims) + [Animation(self.foreground_mobjects)]
          Scene.play(self, *anims, **kwargs)
  
  class MultiplicativeGroupOfComplexNumbers(AdditiveGroupOfComplexNumbers):
      CONFIG = {
          "dot_radius" : Dot.CONFIG["radius"],
          "y_min" : -3*FRAME_Y_RADIUS,
          "y_max" : 3*FRAME_Y_RADIUS,
      }
      def construct(self):
          self.add_plane()
          self.add_title()
          self.fix_zero_and_move_one()
          self.show_example_actions()
          self.show_action_at_i()
          self.show_action_at_i_again()
          self.show_i_squared_is_negative_one()
          self.talk_through_specific_example()
          self.show_break_down()
          self.example_actions_broken_down()
  
      def add_plane(self):
          AdditiveGroupOfComplexNumbers.add_plane(self)
          one_dot = Dot(
              self.z_to_point(1), 
              color = MULTIPLIER_COLOR,
              radius = self.dot_radius,
          )
          self.plane.add(one_dot)
          self.plane.one_dot = one_dot
          self.plane.save_state()
          self.add(self.plane)
  
      def add_title(self):
          title = TextMobject(
              "Multiplicative", "group of", 
              "complex numbers"
          )
          title.to_edge(UP)
          title[0].set_color(MULTIPLIER_COLOR)
          title[2].set_color(BLUE)
          title.add_background_rectangle()
  
          self.play(Write(title, run_time = 2))
          self.wait()
          self.add_foreground_mobjects(title)
  
      def fix_zero_and_move_one(self):
          zero_arrow = Arrow(
              UP+1.25*LEFT, ORIGIN, 
              buff = 2*self.dot_radius
          )
          zero_arrow.set_color(ADDER_COLOR)
          zero_words = TextMobject("Fix zero")
          zero_words.set_color(ADDER_COLOR)
          zero_words.add_background_rectangle()
          zero_words.next_to(zero_arrow.get_start(), UP)
  
          one_point = self.z_to_point(1)
          one_arrow = Arrow(
              one_point+UP+1.25*RIGHT, one_point, 
              buff = 2*self.dot_radius,
              color = MULTIPLIER_COLOR,
          )
          one_words = TextMobject("Drag one")
          one_words.set_color(MULTIPLIER_COLOR)
          one_words.add_background_rectangle()
          one_words.next_to(one_arrow.get_start(), UP)
  
          self.play(
              Write(zero_words, run_time = 2),
              ShowCreation(zero_arrow),
              Indicate(self.plane.zero_dot, color = RED),
          )
          self.play(
              Write(one_words, run_time = 2),
              ShowCreation(one_arrow),
              Indicate(self.plane.one_dot, color = RED),
          )
          self.wait(2)
          self.play(*list(map(FadeOut, [
              zero_words, zero_arrow,
              one_words, one_arrow,
          ])))
  
      def show_example_actions(self):
          z_list = [
              complex(2),
              complex(0.5),
              complex(2, 1),
              complex(-2, 2),
          ]
          for last_z, z in zip([1] + z_list, z_list):
              self.multiply_by_z(z/last_z)
              self.wait()
          self.reset_plane()
          self.wait()
  
      def show_action_at_i(self):
          i_point = self.z_to_point(complex(0, 1))
          i_dot = Dot(i_point)
          i_dot.set_color(RED)
          i_arrow = Arrow(i_point+UP+LEFT, i_point)
          i_arrow.set_color(i_dot.get_color())
  
          arc = Arc(
              start_angle = np.pi/24,
              angle = 10*np.pi/24,
              radius = self.z_to_point(1)[0],
              num_anchors = 20,
          )
          arc.add_tip(tip_length = 0.15)
          arc.set_color(YELLOW)
  
          self.play(
              ShowCreation(i_arrow),
              DrawBorderThenFill(i_dot)
          )
          self.wait()
          self.play(
              FadeOut(i_arrow),
              ShowCreation(arc)
          )
          self.add_foreground_mobjects(arc)
          self.wait(2)
          self.multiply_by_z(complex(0, 1), run_time = 3)
          self.remove(i_dot)
          self.wait()
  
          self.turn_arrow = arc
  
      def show_action_at_i_again(self):
          neg_one_label = [m for m in self.real_labels if m.get_tex_string() == "-1"][0]
          half_turn_arc = Arc(
              start_angle = np.pi/12,
              angle = 10*np.pi/12,
              color = self.turn_arrow.get_color()
          )
          half_turn_arc.add_tip(tip_length = 0.15)
  
          self.multiply_by_z(complex(0, 1), run_time = 3)
          self.wait()
          self.play(Transform(
              self.turn_arrow, half_turn_arc,
              path_arc = np.pi/2
          ))
          self.wait()        
          self.play(Indicate(neg_one_label, run_time = 2))
          self.wait()
          self.foreground_mobjects.remove(self.turn_arrow)
          self.reset_plane(FadeOut(self.turn_arrow))
  
      def show_i_squared_is_negative_one(self):
          equation = TexMobject("i", "\\cdot", "i", "=", "-1")
          terms = equation[::2]
          equation.add_background_rectangle()
          equation.next_to(ORIGIN, RIGHT)
          equation.shift(1.5*UP)
          equation.set_color(MULTIPLIER_COLOR)
  
          self.play(Write(equation, run_time = 2))
          self.wait()
          for term in terms[:2]:
              self.multiply_by_z(
                  complex(0, 1),
                  added_anims = [
                      Animation(equation),
                      Indicate(term, color = RED, run_time = 2)
                  ]
              )
              self.wait()
          self.play(Indicate(terms[-1], color = RED, run_time = 2))
          self.wait()
          self.reset_plane(FadeOut(equation))
  
      def talk_through_specific_example(self):
          z = complex(2, 1)
          angle = np.angle(z)
          point = self.z_to_point(z)
          dot = Dot(point, color = WHITE)
          label = TexMobject("%d + %di"%(z.real, z.imag))
          label.add_background_rectangle()
          label.next_to(dot, UP+RIGHT, buff = 0)
  
          brace = Brace(
              Line(ORIGIN, self.z_to_point(np.sqrt(5))),
              UP
          )
          brace_text = brace.get_text("$\\sqrt{5}$")
          brace_text.add_background_rectangle()
          brace_text.scale(0.7, about_point = brace.get_top())
          brace.rotate(angle)
          brace_text.rotate(angle).rotate_in_place(-angle)
          VGroup(brace, brace_text).set_color(MAROON_B)
          arc = Arc(angle, color = WHITE, radius = 0.5)
          angle_label = TexMobject("30^\\circ")
          angle_label.scale(0.7)
          angle_label.next_to(
              arc, RIGHT, 
              buff = SMALL_BUFF, aligned_edge = DOWN
          )
          angle_label.set_color(MULTIPLIER_COLOR)
  
          self.play(
              Write(label),
              DrawBorderThenFill(dot)
          )
          self.add_foreground_mobjects(label, dot)
          self.wait()
          self.multiply_by_z(z, run_time = 3)
          self.wait()
          self.reset_plane()
          self.multiply_by_z(
              np.exp(complex(0, 1)*angle),
              added_anims = [
                  ShowCreation(arc, run_time = 2),
                  Write(angle_label)
              ]
          )
          self.add_foreground_mobjects(arc, angle_label)
          self.wait()
          self.play(
              GrowFromCenter(brace),
              Write(brace_text)
          )
          self.add_foreground_mobjects(brace, brace_text)
          self.multiply_by_z(np.sqrt(5), run_time = 3)
          self.wait(2)
          to_remove = [
              label, dot,
              brace, brace_text, 
              arc, angle_label,
          ]
          for mob in to_remove:
              self.foreground_mobjects.remove(mob)
          self.reset_plane(*list(map(FadeOut, to_remove)))
          self.wait()
  
      def show_break_down(self):
          positive_reals = Line(ORIGIN, FRAME_X_RADIUS*RIGHT)
          positive_reals.set_color(MAROON_B)
          circle = Circle(
              radius = self.z_to_point(1)[0],
              color = MULTIPLIER_COLOR
          )
          real_actions = [3, 0.5, 1]
          rotation_actions = [
              np.exp(complex(0, angle))
              for angle in np.linspace(0, 2*np.pi, 4)[1:]
          ]
  
          self.play(ShowCreation(positive_reals))
          self.add_foreground_mobjects(positive_reals)
          for last_z, z in zip([1]+real_actions, real_actions):
              self.multiply_by_z(z/last_z)
          self.wait()
          self.play(ShowCreation(circle))
          self.add_foreground_mobjects(circle)
          for last_z, z in zip([1]+rotation_actions, rotation_actions):
              self.multiply_by_z(z/last_z, run_time = 3)
          self.wait()
  
      def example_actions_broken_down(self):
          z_list = [
              complex(2, -1),
              complex(-2, -3),
              complex(0.5, 0.5),
          ]
          for z in z_list:
              dot = Dot(self.z_to_point(z))
              dot.set_color(WHITE)
              dot.save_state()
              dot.move_to(self.plane.one_dot)
              dot.set_fill(opacity = 1)
  
              norm = np.abs(z)
              angle = np.angle(z)
              rot_z = np.exp(complex(0, angle))
  
              self.play(dot.restore)
              self.multiply_by_z(norm)
              self.wait()
              self.multiply_by_z(rot_z)
              self.wait()
              self.reset_plane(FadeOut(dot))
  
      ##
  
      def multiply_by_z(self, z, run_time = 2, **kwargs):
          target = self.plane.copy()
          target.apply_complex_function(lambda w : z*w)
          for dot in target.zero_dot, target.one_dot:
              dot.set_width(2*self.dot_radius)
          angle = np.angle(z)
          kwargs["path_arc"] = kwargs.get("path_arc", angle)
          self.play(
              Transform(self.plane, target, run_time = run_time, **kwargs),
              *kwargs.get("added_anims", [])
          )
  
      def reset_plane(self, *added_anims):
          self.play(FadeOut(self.plane), *added_anims)
          self.plane.restore()
          self.play(FadeIn(self.plane))
  
  class ExponentsAsRepeatedMultiplication(TeacherStudentsScene):
      def construct(self):
          self.show_repeated_multiplication()
          self.show_non_counting_exponents()
  
      def show_repeated_multiplication(self):
          three_twos = TexMobject("2 \\cdot 2 \\cdot 2")
          five_twos = TexMobject("2 \\cdot "*4 + "2")
          exponents = []
          teacher_corner = self.get_teacher().get_corner(UP+LEFT)
          for twos in three_twos, five_twos:
              twos.next_to(teacher_corner, UP)
              twos.generate_target()
              d = sum(np.array(list(twos.get_tex_string())) == "2")
              exponents.append(d)
              twos.brace = Brace(twos, UP)
              twos.exp = twos.brace.get_text("$2^%d$"%d)
              twos.generate_target()
              twos.brace_anim = MaintainPositionRelativeTo(
                  VGroup(twos.brace, twos.exp), twos
              )
  
          self.play(
              GrowFromCenter(three_twos.brace),
              Write(three_twos.exp),
              self.get_teacher().change_mode, "raise_right_hand",
          )
          for mob in three_twos:
              self.play(Write(mob, run_time = 1))
          self.change_student_modes(*["pondering"]*3)
          self.wait(2)
          self.play(
              FadeIn(five_twos.brace),
              FadeIn(five_twos.exp),
              three_twos.center,
              three_twos.to_edge, UP, 2*LARGE_BUFF,
              three_twos.brace_anim,
          )
          self.play(FadeIn(
              five_twos, 
              run_time = 3,
              lag_ratio = 0.5
          ))
          self.wait(2)
  
          cdot = TexMobject("\\cdot")
          lhs = TexMobject("2^{%d + %d} = "%tuple(exponents))
          rule = VGroup(
              lhs, three_twos.target, cdot, five_twos.target
          )
          rule.arrange()
          lhs.next_to(three_twos.target, LEFT, aligned_edge = DOWN)
          rule.next_to(self.get_pi_creatures(), UP)
  
          self.play(
              MoveToTarget(three_twos),
              three_twos.brace_anim,
              MoveToTarget(five_twos),
              five_twos.brace_anim,
              Write(cdot),
              self.get_teacher().change_mode, "happy",
          )
          self.wait()
          self.play(Write(lhs))
          self.wait()
          self.change_student_modes(*["happy"]*3)
          self.wait()
  
          general_equation = TexMobject("2^{x+y}=", "2^x", "2^y")
          general_equation.to_edge(UP, buff = MED_LARGE_BUFF)
          general_equation[0].set_color(GREEN_B)
          VGroup(*general_equation[1:]).set_color(MULTIPLIER_COLOR)
          self.play(*[
              ReplacementTransform(
                  mob.copy(), term, run_time = 2
              )
              for term, mob in zip(general_equation, [
                  lhs, three_twos.exp, five_twos.exp
              ])
          ])
          self.wait(2)
  
          self.exponential_rule = general_equation
          self.expanded_exponential_rule = VGroup(
              lhs, three_twos, three_twos.brace, three_twos.exp,
              cdot, five_twos, five_twos.brace, five_twos.exp,
          )
  
      def show_non_counting_exponents(self):
          self.play(
              self.expanded_exponential_rule.scale, 0.5,
              self.expanded_exponential_rule.to_corner, UP+LEFT
          )
          half_power, neg_power, imag_power = alt_powers = VGroup(
              TexMobject("2^{1/2}"),
              TexMobject("2^{-1}"),
              TexMobject("2^{i}"),
          )
          alt_powers.arrange(RIGHT, buff = LARGE_BUFF)
          alt_powers.next_to(self.get_students(), UP, buff = LARGE_BUFF)
  
          self.play(
              Write(half_power, run_time = 2),
              *[
                  ApplyMethod(pi.change_mode, "pondering")
                  for pi in self.get_pi_creatures()
              ]
          )
          for mob in alt_powers[1:]:
              self.play(Write(mob, run_time = 1))
              self.wait()
          self.wait()
          self.play(*it.chain(*[
              [pi.change_mode, "confused", pi.look_at, half_power]
              for pi in self.get_students()
          ]))
          for power in alt_powers[:2]:
              self.play(Indicate(power))
              self.wait()
          self.wait()
  
          self.teacher_says("Extend the \\\\ definition")
          self.change_student_modes("pondering", "confused", "erm")
          self.wait()
  
          half_expression = TexMobject(
              "\\big(", "2^{1/2}", "\\big)", 
              "\\big(2^{1/2}\\big) = 2^{1}"
          )
          neg_one_expression = TexMobject(
              "\\big(", "2^{-1}", "\\big)", 
              "\\big( 2^{1} \\big) = 2^{0}"
          )
          expressions = VGroup(half_expression, neg_one_expression)
          expressions.arrange(
              DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF
          )
          expressions.next_to(self.get_students(), UP, buff = LARGE_BUFF)
          expressions.to_edge(LEFT)
  
          self.play(
              Transform(half_power, half_expression[1]),
              Write(half_expression),            
              RemovePiCreatureBubble(self.get_teacher()),            
          )
          self.wait()
          self.play(
              Transform(neg_power, neg_one_expression[1]),
              Write(neg_one_expression)
          )
          self.wait(2)
          self.play(
              self.exponential_rule.next_to,
              self.get_teacher().get_corner(UP+LEFT), UP, MED_LARGE_BUFF,
              self.get_teacher().change_mode, "raise_right_hand",
          )
          self.wait(2)
          self.play(
              imag_power.move_to, UP,
              imag_power.scale_in_place, 1.5,
              imag_power.set_color, BLUE,
              self.exponential_rule.to_edge, RIGHT,
              self.get_teacher().change_mode, "speaking"
          )
          self.play(*it.chain(*[
              [pi.change_mode, "pondering", pi.look_at, imag_power]
              for pi in self.get_students()
          ]))
          self.wait()
  
          group_theory_words = TextMobject("Group theory?")
          group_theory_words.next_to(
              self.exponential_rule, UP, buff = LARGE_BUFF
          )
          arrow = Arrow(
              group_theory_words,
              self.exponential_rule,
              color = WHITE,
              buff = SMALL_BUFF
          )
          group_theory_words.shift_onto_screen()
          self.play(
              Write(group_theory_words), 
              ShowCreation(arrow)
          )
          self.wait(2)
  
  class ExponentsAsHomomorphism(Scene):
      CONFIG = {
          "top_line_center" : 2.5*UP,
          "top_line_config" : {
              "x_min" : -16,
              "x_max" : 16,
          },
          "bottom_line_center" : 2.5*DOWN,
          "bottom_line_config" : {
              "x_min" : -FRAME_WIDTH,
              "x_max" : FRAME_WIDTH,
          }
      }
      def construct(self):
          self.comment_on_equation()
          self.show_adders()
          self.show_multipliers()
          self.confused_at_mapping()
          self.talk_through_composition()
          self.add_quote()
  
      def comment_on_equation(self):
          equation = TexMobject(
              "2", "^{x", "+", "y}", "=", "2^x", "2^y"
          )
          lhs = VGroup(*equation[:4])
          rhs = VGroup(*equation[5:])
          lhs_brace = Brace(lhs, UP)
          lhs_text = lhs_brace.get_text("Add inputs")
          lhs_text.set_color(GREEN_B)
          rhs_brace = Brace(rhs, DOWN)
          rhs_text = rhs_brace.get_text("Multiply outputs")
          rhs_text.set_color(MULTIPLIER_COLOR)
  
          self.add(equation)
          for brace, text in (lhs_brace, lhs_text), (rhs_brace, rhs_text):
              self.play(
                  GrowFromCenter(brace),
                  Write(text)
              )
              self.wait()
          self.wait()
  
          self.equation = equation
          self.lhs_brace_group = VGroup(lhs_brace, lhs_text)
          self.rhs_brace_group = VGroup(rhs_brace, rhs_text)
  
      def show_adders(self):
          equation = self.equation
          adders = VGroup(equation[1], equation[3]).copy()
          top_line = NumberLine(**self.top_line_config)
          top_line.add_numbers()
          top_line.shift(self.top_line_center)
  
          self.play(
              adders.scale, 1.5,
              adders.center,
              adders.space_out_submobjects, 2,
              adders.to_edge, UP,
              adders.set_color, GREEN_B,
              FadeOut(self.lhs_brace_group),
              Write(top_line)
          )
          self.wait()
          for x in 3, 5, -8:
              self.play(top_line.shift, x*RIGHT, run_time = 2)
              self.wait()
  
          self.top_line = top_line
          self.adders = adders
  
      def show_multipliers(self):
          equation = self.equation
          multipliers = VGroup(*self.equation[-2:]).copy()
  
          bottom_line = NumberLine(**self.bottom_line_config)
          bottom_line.add_numbers()
          bottom_line.shift(self.bottom_line_center)
  
          self.play(
              multipliers.space_out_submobjects, 4,
              multipliers.next_to, self.bottom_line_center, 
                  UP, MED_LARGE_BUFF,
              multipliers.set_color, YELLOW,
              FadeOut(self.rhs_brace_group),
              Write(bottom_line),
          )
          stretch_kwargs = {
          }
          for x in 3, 1./5, 5./3:
              self.play(
                  self.get_stretch_anim(bottom_line, x),
                  run_time = 3
              )
          self.wait()
  
          self.bottom_line = bottom_line
          self.multipliers = multipliers
  
      def confused_at_mapping(self):
          arrow = Arrow(
              self.top_line.get_bottom()[1]*UP,
              self.bottom_line.get_top()[1]*UP,
              color = WHITE
          )
          randy = Randolph(mode = "confused")
          randy.scale(0.75)
          randy.flip()
          randy.next_to(arrow, RIGHT, LARGE_BUFF)
          randy.look_at(arrow.get_top())
  
          self.play(self.equation.to_edge, LEFT)
          self.play(
              ShowCreation(arrow),
              FadeIn(randy)
          )
          self.play(randy.look_at, arrow.get_bottom())
          self.play(Blink(randy))
          self.wait()
          for x in 1, -2, 3, 1, -3:
              self.play(
                  self.get_stretch_anim(self.bottom_line, 2**x),
                  self.top_line.shift, x*RIGHT,
                  randy.look_at, self.top_line,
                  run_time = 2
              )
              if random.random() < 0.3:
                  self.play(Blink(randy))
              else:
                  self.wait()
  
          self.randy = randy
  
      def talk_through_composition(self):
          randy = self.randy
          terms = list(self.adders) + list(self.multipliers)
          inputs = [-1, 2]
          target_texs = list(map(str, inputs))
          target_texs += ["2^{%d}"%x for x in inputs]
          for mob, target_tex in zip(terms, target_texs):
              target = TexMobject(target_tex)
              target.set_color(mob[0].get_color())
              target.move_to(mob, DOWN)
              if mob in self.adders:
                  target.to_edge(UP)
              mob.target = target
  
          self.play(
              self.equation.next_to, ORIGIN, LEFT, MED_LARGE_BUFF,
              randy.change_mode, "pondering",
              randy.look_at, self.equation
          )
          self.wait()
          self.play(randy.look_at, self.top_line)
          self.show_composition(
              *inputs,
              parallel_anims = list(map(MoveToTarget, self.adders))
          )
          self.play(
              FocusOn(self.bottom_line_center),
              randy.look_at, self.bottom_line_center,
          )
          self.show_composition(
              *inputs,
              parallel_anims = list(map(MoveToTarget, self.multipliers))
          )
          self.wait()
  
      def add_quote(self):
          brace = Brace(self.equation, UP)
          quote = TextMobject("``Preserves the group structure''")
          quote.add_background_rectangle()
          quote.next_to(brace, UP)
  
          self.play(
              GrowFromCenter(brace),
              Write(quote),
              self.randy.look_at, quote,
          )
          self.play(self.randy.change_mode, "thinking")
          self.play(Blink(self.randy))
          self.wait()
          self.show_composition(-1, 2)
          self.wait()
  
      ####
  
      def show_composition(self, *inputs, **kwargs):
          parallel_anims = kwargs.get("parallel_anims", [])
          for x in range(len(inputs) - len(parallel_anims)):
              parallel_anims.append(Animation(Mobject()))
  
          for line in self.top_line, self.bottom_line:
              line.save_state()
  
          for x, parallel_anim in zip(inputs, parallel_anims):
              anims = [
                  ApplyMethod(self.top_line.shift, x*RIGHT),
                  self.get_stretch_anim(self.bottom_line, 2**x),
              ]
              for anim in anims:
                  anim.set_run_time(2)
              self.play(parallel_anim)
              self.play(*anims)
              self.wait()
          self.play(*[
              line.restore 
              for line in (self.top_line, self.bottom_line)
          ])
  
      def get_stretch_anim(self, bottom_line, x):
          target = bottom_line.copy()
          target.stretch_about_point(
              x, 0, self.bottom_line_center,
          )
          for number in target.numbers:
              number.stretch_in_place(1./x, dim = 0)
          return Transform(bottom_line, target)
  
  class DihedralCubeHomomorphism(GroupOfCubeSymmetries, SymmetriesOfSquare):
      def construct(self):
          angle_axis_pairs = [
              (np.pi/2, OUT),
              (np.pi, RIGHT),
              (np.pi, OUT),
              (np.pi, UP+RIGHT),
              (-np.pi/2, OUT),
              (np.pi, UP+LEFT),
          ]
          angle_axis_pairs *= 3
  
          title = TextMobject(
              "``", "Homo", "morph", "ism", "''", 
              arg_separator = ""
          )
          homo_brace = Brace(title[1], UP, buff = SMALL_BUFF)
          homo_def = homo_brace.get_text("same")
          morph_brace = Brace(title[2], UP, buff = SMALL_BUFF)
          morph_def = morph_brace.get_text("shape", buff = SMALL_BUFF)
          def_group = VGroup(
              homo_brace, homo_def, 
              morph_brace, morph_def
          )
          VGroup(title, def_group).to_edge(UP)
          homo_group = VGroup(title[1], homo_brace, homo_def)
          morph_group = VGroup(title[2], morph_brace, morph_def)
  
          equation = TexMobject("f(X \\circ Y) = f(X) \\circ f(Y)")
          equation.next_to(title, DOWN)
  
          self.add(title, equation)
  
          arrow = Arrow(LEFT, RIGHT)
          cube = self.get_cube()
          cube.next_to(arrow, RIGHT)
          pose_matrix = self.get_pose_matrix()
  
          square = self.square = Square(**self.square_config)
          self.add_randy_to_square(square)
          square.next_to(arrow, LEFT)
  
          VGroup(square, arrow, cube).next_to(
              equation, DOWN, buff = MED_LARGE_BUFF
          )
  
          self.add(square, cube)
          self.play(ShowCreation(arrow))
          for i, (angle, raw_axis) in enumerate(angle_axis_pairs):
              posed_axis = np.dot(raw_axis, pose_matrix.T)
              self.play(*[
                  Rotate(
                      mob, angle = angle, axis = axis, 
                      in_place = True,
                      run_time = abs(angle/(np.pi/2))
                  )
                  for mob, axis in [(square, raw_axis), (cube, posed_axis)]
              ])
              self.wait()
              if i == 2:
                  for group, color in (homo_group, YELLOW), (morph_group, BLUE):
                      part, remainder = group[0], VGroup(*group[1:])
                      remainder.set_color(color)
                      self.play(
                          part.set_color, color,
                          FadeIn(remainder)
                      )
  
  class ComplexExponentiationAbstract():
      CONFIG = {
          "start_base" : 2,
          "new_base" : 5,
          "group_type" : None,
          "color" : None,
          "vect" : None,
      }
      def construct(self):
          self.base = self.start_base
          example_inputs = [2, -3, 1]
          self.add_vertical_line()
          self.add_plane_unanimated()
          self.add_title()
          self.add_arrow()
          self.show_example(complex(1, 1))
          self.draw_real_line()
          self.show_real_actions(*example_inputs)
          self.show_pure_imaginary_actions(*example_inputs)
          self.set_color_vertical_line()
          self.set_color_unit_circle()
          self.show_pure_imaginary_actions(*example_inputs)
          self.walk_input_up_vertical()
          self.change_base(self.new_base, str(self.new_base))
          self.walk_input_up_vertical()
          self.change_base(np.exp(1), "e")
          self.take_steps_for_e()
          self.write_eulers_formula()
          self.show_pure_imaginary_actions(-np.pi, np.pi)
          self.wait()
  
      def add_vertical_line(self):
          line = Line(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN)
          line.set_stroke(color = self.color, width = 10)
          line.shift(-FRAME_X_RADIUS*self.vect/2)
          self.add(line)
          self.add_foreground_mobjects(line)
  
      def add_plane_unanimated(self):
          should_skip_animations = self.skip_animations
          self.skip_animations = True
          self.add_plane()
          self.skip_animations = should_skip_animations
  
      def add_title(self):
          title = TextMobject(self.group_type, "group")
          title.scale(0.8)
          title[0].set_color(self.color)
          title.add_background_rectangle()
          title.to_edge(UP, buff = MED_SMALL_BUFF)
          self.add_foreground_mobjects(title)
  
      def add_arrow(self):
          arrow = Arrow(LEFT, RIGHT, color = WHITE)
          arrow.move_to(-FRAME_X_RADIUS*self.vect/2 + 2*UP)
          arrow.set_stroke(width = 6),
          func_mob = TexMobject("2^x")    
          func_mob.next_to(arrow, UP, aligned_edge = LEFT)
          func_mob.add_background_rectangle()
  
          self.add_foreground_mobjects(arrow, func_mob)
          self.wait()
          self.func_mob = func_mob
  
      def show_example(self, z):
          self.apply_action(
              z,
              run_time = 5,
              rate_func = there_and_back
          )
  
      def draw_real_line(self):
          line = VGroup(Line(ORIGIN, FRAME_X_RADIUS*RIGHT))
          if self.vect[0] < 0:
              line.add(Line(ORIGIN, FRAME_X_RADIUS*LEFT))
          line.set_color(RED)
  
          self.play(*list(map(ShowCreation, line)), run_time = 3)
          self.add_foreground_mobjects(line)
  
          self.real_line = line
  
      def show_real_actions(self, *example_inputs):
          for x in example_inputs:
              self.apply_action(x)
              self.wait()
  
      def show_pure_imaginary_actions(self, *example_input_imag_parts):
          for y in example_input_imag_parts:
              self.apply_action(complex(0, y), run_time = 3)
              self.wait()
  
      def change_base(self, new_base, new_base_tex):
          new_func_mob = TexMobject(new_base_tex + "^x")
          new_func_mob.add_background_rectangle()
          new_func_mob.move_to(self.func_mob)
  
          self.play(FocusOn(self.func_mob))
          self.play(Transform(self.func_mob, new_func_mob))
          self.wait()
          self.base = new_base
  
      def write_eulers_formula(self):
          formula = TexMobject("e^", "{\\pi", "i}", "=", "-1")
          VGroup(*formula[1:3]).set_color(ADDER_COLOR)
          formula[-1].set_color(MULTIPLIER_COLOR)
          formula.scale(1.5)
          formula.next_to(ORIGIN, UP)
          formula.shift(-FRAME_X_RADIUS*self.vect/2)
          for part in formula:
              part.add_to_back(BackgroundRectangle(part))
  
          Scene.play(self, Write(formula))
          self.add_foreground_mobjects(formula)
          self.wait(2)
  
  class ComplexExponentiationAdderHalf(
      ComplexExponentiationAbstract, 
      AdditiveGroupOfComplexNumbers
      ):
      CONFIG = {
          "group_type" : "Additive",
          "color" : GREEN_B,
          "vect" : LEFT,
      }
      def construct(self):
          ComplexExponentiationAbstract.construct(self)
  
      def apply_action(self, z, run_time = 2, **kwargs):
          kwargs["run_time"] = run_time
          self.play(
              ApplyMethod(
                  self.plane.shift, self.z_to_point(z),
                  **kwargs
              ),
              *kwargs.get("added_anims", [])
          )
  
      def set_color_vertical_line(self):
          line = VGroup(
              Line(ORIGIN, FRAME_Y_RADIUS*UP),
              Line(ORIGIN, FRAME_Y_RADIUS*DOWN),
          )
          line.set_color(YELLOW)
  
          self.play(
              FadeOut(self.real_line),
              *list(map(ShowCreation, line))
          )
          self.foreground_mobjects.remove(self.real_line)
          self.play(
              line.rotate, np.pi/24,
              rate_func = wiggle,
          )
          self.wait()
  
          self.foreground_mobjects = [line] + self.foreground_mobjects
          self.vertical_line = line
  
      def set_color_unit_circle(self):
          line = VGroup(
              Line(ORIGIN, FRAME_Y_RADIUS*UP),
              Line(ORIGIN, FRAME_Y_RADIUS*DOWN),
          )
          line.set_color(YELLOW)
          for submob in line:
              submob.insert_n_curves(10)
              submob.make_smooth()
          circle = VGroup(
              Circle(),
              Circle().flip(RIGHT),
          )
          circle.set_color(YELLOW)
          circle.shift(FRAME_X_RADIUS*RIGHT)
  
          self.play(ReplacementTransform(
              line, circle, run_time = 3
          ))
          self.remove(circle)
          self.wait()
  
      def walk_input_up_vertical(self):
          arrow = Arrow(ORIGIN, UP, buff = 0, tip_length = 0.15)
          arrow.set_color(GREEN)
          brace = Brace(arrow, RIGHT, buff = SMALL_BUFF)
          brace_text = brace.get_text("1 unit")
          brace_text.add_background_rectangle()
  
          Scene.play(self, ShowCreation(arrow))
          self.add_foreground_mobjects(arrow)
          self.play(
              GrowFromCenter(brace),
              Write(brace_text, run_time = 1)
          )
          self.add_foreground_mobjects(brace, brace_text)        
          self.wait()
          self.apply_action(complex(0, 1))
          self.wait(7)##Line up with MultiplierHalf
  
          to_remove = arrow, brace, brace_text
          for mob in to_remove:
              self.foreground_mobjects.remove(mob)
          self.play(*list(map(FadeOut, to_remove)))
          self.apply_action(complex(0, -1))
  
      def take_steps_for_e(self):
          slide_values = [1, 1, 1, np.pi-3]
          braces = [
              Brace(Line(ORIGIN, x*UP), RIGHT, buff = SMALL_BUFF)
              for x in np.cumsum(slide_values)
          ]
          labels = list(map(TextMobject, [
              "1 unit",
              "2 units",
              "3 units",
              "$\\pi$ units",
          ]))
          for label, brace in zip(labels, braces):
              label.add_background_rectangle()
              label.next_to(brace, RIGHT, buff = SMALL_BUFF)
  
          curr_brace = None
          curr_label = None
          for slide_value, label, brace in zip(slide_values, labels, braces):
              self.apply_action(complex(0, slide_value))
              if curr_brace is None:
                  curr_brace = brace
                  curr_label = label
                  self.play(
                      GrowFromCenter(curr_brace),
                      Write(curr_label)
                  )
                  self.add_foreground_mobjects(brace, label)
              else:
                  self.play(
                      Transform(curr_brace, brace),
                      Transform(curr_label, label),
                  )
              self.wait()
              self.wait(4) ##Line up with multiplier half
  
  class ComplexExponentiationMultiplierHalf(
      ComplexExponentiationAbstract, 
      MultiplicativeGroupOfComplexNumbers
      ):
      CONFIG = {
          "group_type" : "Multiplicative",
          "color" : MULTIPLIER_COLOR,
          "vect" : RIGHT,
      }
  
      def construct(self):
          ComplexExponentiationAbstract.construct(self)
  
      def apply_action(self, z, run_time = 2, **kwargs):
          kwargs["run_time"] = run_time
          self.multiply_by_z(self.base**z, **kwargs)
  
      def set_color_vertical_line(self):
          self.play(FadeOut(self.real_line))
          self.foreground_mobjects.remove(self.real_line)
          self.wait(2)
  
      def set_color_unit_circle(self):
          line = VGroup(
              Line(ORIGIN, FRAME_Y_RADIUS*UP),
              Line(ORIGIN, FRAME_Y_RADIUS*DOWN),
          )
          line.set_color(YELLOW)
          line.shift(FRAME_X_RADIUS*LEFT)
          for submob in line:
              submob.insert_n_curves(10)
              submob.make_smooth()
          circle = VGroup(
              Circle(),
              Circle().flip(RIGHT),
          )
          circle.set_color(YELLOW)
  
          self.play(ReplacementTransform(
              line, circle, run_time = 3
          ))
          self.add_foreground_mobjects(circle)
          self.wait()
  
      def walk_input_up_vertical(self):
          output_z = self.base**complex(0, 1)
          angle = np.angle(output_z)
  
          arc, brace, curved_brace, radians_label = \
              self.get_arc_braces_and_label(angle)
  
          self.wait(3)
          self.apply_action(complex(0, 1))
  
          Scene.play(self, ShowCreation(arc))
          self.add_foreground_mobjects(arc)
          self.play(GrowFromCenter(brace))
          self.play(Transform(brace, curved_brace))
          self.play(Write(radians_label, run_time = 2))
          self.wait(2)
  
          self.foreground_mobjects.remove(arc)
          self.play(*list(map(FadeOut, [arc, brace, radians_label])))
          self.apply_action(complex(0, -1))
  
      def get_arc_braces_and_label(self, angle):
          arc = Arc(angle)
          arc.set_stroke(GREEN, width = 6)
          arc_line = Line(RIGHT, RIGHT+angle*UP)
          brace = Brace(arc_line, RIGHT, buff = 0)
          for submob in brace.family_members_with_points():
              submob.insert_n_curves(10)
          curved_brace = brace.copy()
          curved_brace.shift(LEFT)
          curved_brace.apply_complex_function(
              np.exp, maintain_smoothness = False
          )
  
          half_point = arc.point_from_proportion(0.5)
          radians_label = TexMobject("%.3f"%angle)
          radians_label.add_background_rectangle()
          radians_label.next_to(
              1.5*half_point, np.round(half_point), buff = 0
          )
  
          return arc, brace, curved_brace, radians_label
  
      def take_steps_for_e(self):
          angles = [1, 2, 3, np.pi]
  
          curr_brace = None
          curr_label = None
          curr_arc = None
          for last_angle, angle in zip([0]+angles, angles):
              arc, brace, curved_brace, label = self.get_arc_braces_and_label(angle)
              if angle == np.pi:
                  label = TexMobject("%.5f\\dots"%np.pi)
                  label.add_background_rectangle(opacity = 1)
                  label.next_to(curved_brace, UP, buff = SMALL_BUFF)
  
              self.apply_action(complex(0, angle-last_angle))
              self.wait(2)#Line up with Adder half
              if curr_brace is None:
                  curr_brace = curved_brace
                  curr_label = label
                  curr_arc = arc
                  brace.set_fill(opacity = 0)
                  Scene.play(self, ShowCreation(curr_arc))
                  self.add_foreground_mobjects(curr_arc)
                  self.play(
                      ReplacementTransform(brace, curr_brace),
                      Write(curr_label)
                  )
                  self.add_foreground_mobjects(curr_brace, curr_label)
              else:
                  Scene.play(self, ShowCreation(arc))
                  self.add_foreground_mobjects(arc)
                  self.foreground_mobjects.remove(curr_arc)
                  self.remove(curr_arc)
                  curr_arc = arc
                  self.play(
                      Transform(curr_brace, curved_brace),
                      Transform(curr_label, label),
                  )
              self.wait()
              self.wait()
  
  class ExpComplexHomomorphismPreviewAbstract(ComplexExponentiationAbstract):
      def construct(self):
          self.base = self.start_base
  
          self.add_vertical_line()
          self.add_plane_unanimated()
          self.add_title()
          self.add_arrow()
          self.change_base(np.exp(1), "e")
          self.write_eulers_formula()
          self.show_pure_imaginary_actions(np.pi, 0, -np.pi)
          self.wait()
  
  class ExpComplexHomomorphismPreviewAdderHalf(
      ExpComplexHomomorphismPreviewAbstract,
      ComplexExponentiationAdderHalf
      ):
      def construct(self):
          ExpComplexHomomorphismPreviewAbstract.construct(self)
  
  class ExpComplexHomomorphismPreviewMultiplierHalf(
      ExpComplexHomomorphismPreviewAbstract,
      ComplexExponentiationMultiplierHalf
      ):
      def construct(self):
          ExpComplexHomomorphismPreviewAbstract.construct(self)
  
  class WhyE(TeacherStudentsScene):
      def construct(self):
          self.student_says("Why e?")
          self.play(self.get_teacher().change_mode, "pondering")
          self.wait(3)
  
  class ReadFormula(Scene):
      def construct(self):
          formula = TexMobject("e^", "{\\pi i}", "=", "-1")
          formula[1].set_color(GREEN_B)
          formula[3].set_color(MULTIPLIER_COLOR)
          formula.scale(2)
  
          randy = Randolph()
          randy.shift(2*LEFT)
          formula.next_to(randy, RIGHT, aligned_edge = UP)
          randy.look_at(formula)
  
          self.add(randy, formula)
          self.wait()
          self.play(randy.change_mode, "thinking")
          self.wait()
          self.play(Blink(randy))
          self.wait(3)
  
  class EfvgtPatreonThanks(PatreonThanks):
      CONFIG = {
          "specific_patrons" : [
              "Ali Yahya",
              "Meshal  Alshammari",
              "CrypticSwarm    ",
              "Justin Helps",
              "Ankit Agarwal",
              "Yu  Jun",
              "Shelby  Doolittle",
              "Dave    Nicponski",
              "Damion  Kistler",
              "Juan    Benet",
              "Othman  Alikhan",
              "Markus  Persson",
              "Dan Buchoff",
              "Derek Dai",
              "Joseph  John Cox",
              "Luc Ritchie",
              "Nils Schneider",
              "Mathew Bramson",
              "Guido   Gambardella",
              "Jerry   Ling",
              "Mark    Govea",
              "Vecht",
              "Shimin Kuang",
              "Rish    Kundalia",
              "Achille Brighton",
              "Kirk    Werklund",
              "Ripta   Pasay",
              "Felipe  Diniz",
          ]
      }
  
  class EmeraldLogo(SVGMobject):
      CONFIG = {
          "file_name" : "emerald_logo",
          "stroke_width" : 0,
          "fill_opacity" : 1,
          # "helix_color" : "#439271",
          "helix_color" : GREEN_E,
      }
      def __init__(self, **kwargs):
          SVGMobject.__init__(self, **kwargs)
          self.set_height(1)
          for submob in self.split()[18:]:
              submob.set_color(self.helix_color)
  
  class ECLPromo(PiCreatureScene):
      CONFIG = {
          "seconds_to_blink" : 4,
      }
      def construct(self):
          logo = EmeraldLogo()
          logo.to_corner(UP+LEFT, buff = MED_SMALL_BUFF)
          logo_part1 = VGroup(*logo[:15])
          logo_part2 = VGroup(*logo[15:])
  
          rect = Rectangle(height = 9, width = 16)
          rect.set_height(5)
          rect.next_to(logo, DOWN)
          rect.to_edge(LEFT)
  
          self.play(
              self.pi_creature.change_mode, "hooray",
              ShowCreation(rect)
          )
          self.wait(3)
          self.play(FadeIn(
              logo_part1, run_time = 3, 
              lag_ratio = 0.5
          ))
          logo_part2.save_state()
          logo_part2.scale(2)
          logo_part2.next_to(self.pi_creature.get_corner(UP+LEFT), UP)
          logo_part2.shift(MED_SMALL_BUFF*RIGHT)
          self.play(
              self.pi_creature.change_mode, "raise_right_hand",
          )        
          self.play(DrawBorderThenFill(logo_part2))
          self.play(
              logo_part2.scale_in_place, 0.5,
              logo_part2.to_edge, UP
          )
          self.play(
              logo_part2.restore,
              self.pi_creature.change_mode, "happy"
          )
          self.play(self.pi_creature.look_at, rect)
          self.wait(10)
          self.play(
              self.pi_creature.change_mode, "pondering",
              self.pi_creature.look, DOWN
          )
          self.wait(10)
  
  class ExpTransformation(ComplexTransformationScene):
      CONFIG = {
          "camera_class": ThreeDCamera,
      }
      def construct(self):
          self.camera.camera_distance = 10,
          self.add_transformable_plane()
          self.prepare_for_transformation(self.plane)
          final_plane = self.plane.copy().apply_complex_function(np.exp)
          cylinder = self.plane.copy().apply_function(
              lambda x_y_z : np.array([x_y_z[0], np.sin(x_y_z[1]), -np.cos(x_y_z[1])])
          )
          title = TexMobject("x \\to e^x")
          title.add_background_rectangle()
          title.scale(1.5)
          title.next_to(ORIGIN, RIGHT)
          title.to_edge(UP, buff = MED_SMALL_BUFF)
          self.add_foreground_mobjects(title)
  
          self.play(Transform(
              self.plane, cylinder, 
              run_time = 3,
              path_arc_axis = RIGHT,
              path_arc = np.pi,
          ))
          self.play(Rotate(
              self.plane, -np.pi/3, UP, 
              run_time = 5
          ))
          self.play(Transform(self.plane, final_plane, run_time = 3))
          self.wait(3)
  
  class Thumbnail(Scene):
      def construct(self):
          formula = TexMobject("e^", "{\\pi i}", "=", "-1")
          formula[1].set_color(GREEN_B)
          formula[3].set_color(YELLOW)
          formula.scale(4)
          formula.to_edge(UP, buff = LARGE_BUFF)
          self.add(formula)
  
          via = TextMobject("via")
          via.scale(2)
          groups = TextMobject("Group theory")
          groups.scale(3)
          groups.to_edge(DOWN)
          via.move_to(VGroup(formula, groups))
          self.add(via, groups)
  
  
  
        
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  