def get_vector_perp_to_p_and_q(p, q):
  """p and q are distinct points on sphere, return a unit vector perpendicular to both"""
  if abs(dot(p,q) + 1) < 0.0001: ### deal with the awkward special case when p and q are antipodal on the sphere
      if abs(dot(p, vector([1,0,0]))) > 0.9999: #p is parallel to (1,0,0)
        return vector([0,1,0])
      else:
        return cross(p, vector([1,0,0])).normalised() 
  else:
    return cross(p, q).normalised()
def get_vector_perp_to_p_and_q(p, q):
    """p and q are distinct points on sphere, return a unit vector perpendicular to both"""
    if abs(
            dot(p, q) + 1
    ) < 0.0001:  ### deal with the awkward special case when p and q are antipodal on the sphere
        if abs(dot(p, vector([1, 0, 0]))) > 0.9999:  #p is parallel to (1,0,0)
            return vector([0, 1, 0])
        else:
            return cross(p, vector([1, 0, 0])).normalised()
    else:
        return cross(p, q).normalised()
def rotate_sphere_points_p_to_q(p, q):
    """p and q are points on the sphere, return SL(2,C) matrix rotating image of p to image of q on CP^1"""
    p, q = vector(p), vector(q)
    CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
    if abs(dot(p, q) - 1) < 0.0001:
        return [[1, 0], [0, 1]]
    else:
        r = get_vector_perp_to_p_and_q(p, q)
        CP1r, CP1mr = CP1_from_sphere(r), CP1_from_sphere(-r)
        return two_triples_to_SL(CP1p, CP1r, CP1mr, CP1q, CP1r, CP1mr)
def zoom_along_axis_sphere_points_p_q(p, q, zoom_factor):
  """p and q are points on sphere, return SL(2,C) matrix zooming along axis from p to q"""
  p, q = vector(p), vector(q)
  CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
  assert dot(p,q) < 0.9999   #points should not be in the same place
  r = get_vector_perp_to_p_and_q(p, q)
  CP1r = CP1_from_sphere(r)
  M_standardise = two_triples_to_SL(CP1p, CP1q, CP1r, [0,1], [1,0], [1,1])
  M_theta = [[zoom_factor,0],[0,1]] 
  return matrix_mult( matrix_mult(matrix2_inv(M_standardise), M_theta), M_standardise )
def rotate_around_axis_sphere_points_p_q(p,q,theta):
  """p and q are points on sphere, return SL(2,C) matrix rotating by angle theta around the axis from p to q"""
  p, q = vector(p), vector(q)
  CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
  assert dot(p,q) < 0.9999, "axis points should not be in the same place!"
  r = get_vector_perp_to_p_and_q(p, q)
  CP1r = CP1_from_sphere(r)
  M_standardise = two_triples_to_SL(CP1p, CP1q, CP1r, [0,1], [1,0], [1,1])
  M_theta = [[complex(cos(theta),sin(theta)),0],[0,1]] #rotate on axis through 0, infty by theta
  return matrix_mult( matrix_mult(matrix2_inv(M_standardise), M_theta), M_standardise )
def rotate_sphere_points_p_to_q(p, q):
  """p and q are points on the sphere, return SL(2,C) matrix rotating image of p to image of q on CP^1"""
  p, q = vector(p), vector(q)
  CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
  if abs(dot(p,q) - 1) < 0.0001:
    return [[1,0],[0,1]]
  else:
    r = get_vector_perp_to_p_and_q(p, q)
    CP1r, CP1mr = CP1_from_sphere(r), CP1_from_sphere(-r)
    return two_triples_to_SL(CP1p, CP1r, CP1mr, CP1q, CP1r, CP1mr) 
def zoom_along_axis_sphere_points_p_q(p, q, zoom_factor):
    """p and q are points on sphere, return SL(2,C) matrix zooming along axis from p to q"""
    p, q = vector(p), vector(q)
    CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
    assert dot(p, q) < 0.9999  #points should not be in the same place
    r = get_vector_perp_to_p_and_q(p, q)
    CP1r = CP1_from_sphere(r)
    M_standardise = two_triples_to_SL(CP1p, CP1q, CP1r, [0, 1], [1, 0], [1, 1])
    M_theta = [[zoom_factor, 0], [0, 1]]
    return matrix_mult(matrix_mult(matrix2_inv(M_standardise), M_theta),
                       M_standardise)
def rotate_around_axis_sphere_points_p_q(p, q, theta):
    """p and q are points on sphere, return SL(2,C) matrix rotating by angle theta around the axis from p to q"""
    p, q = vector(p), vector(q)
    CP1p, CP1q = CP1_from_sphere(p), CP1_from_sphere(q)
    assert dot(p, q) < 0.9999, "axis points should not be in the same place!"
    r = get_vector_perp_to_p_and_q(p, q)
    CP1r = CP1_from_sphere(r)
    M_standardise = two_triples_to_SL(CP1p, CP1q, CP1r, [0, 1], [1, 0], [1, 1])
    M_theta = [[complex(cos(theta), sin(theta)), 0],
               [0, 1]]  #rotate on axis through 0, infty by theta
    return matrix_mult(matrix_mult(matrix2_inv(M_standardise), M_theta),
                       M_standardise)