from sympy import *
from spb import *
from spb.graphics.vector_transforms import express
from sympy.vector import CoordSys3D
C = CoordSys3D("C")
S = C.create_new("S", transformation="spherical")
x, y, z = C.base_scalars()
r, theta, phi = S.base_scalars()

# position vector for a point on the surface of a sphere of radius r
sphere = r * S.i
# get the parametric equation for a sphere
parameterization = express(sphere, C).to_matrix(C).subs(r, 7)

# normal and tangents to the sphere expressed in the cartesian frame,
# using spherical variables
n = express(sphere, C).normalize().simplify()
t_theta = n.diff(theta).normalize().simplify()
t_phi = n.diff(phi).normalize().simplify()

# normal and tangents to the sphere expressed in the cartesian frame,
# using cartesian variables
d = {k: v for k, v in zip([r, theta, phi], S.transformation_from_parent())}
n = n.subs(d)
t_theta = t_theta.subs(d)
t_phi = t_phi.subs(d)

phi_max = 3 * pi / 2
theta_max = pi / 2
sphere_series = surface_parametric(
    *parameterization, (phi, 0, phi_max), (theta, 0, theta_max))
locations_for_vectors = surface_parametric(
    *parameterization, (phi, 0, phi_max), (theta, 0, theta_max),
    n1=15, n2=8)[0]
quiver_kw=dict(pivot="tail", head_size=2, line_width=0.03)
common_kws = dict(
    # NOTE: dummy values for ranges. The important thing is
    # to order them appropriately. For example, variable x must go
    # on range_x, etc.
    range_x=(x, -1, 1), range_y=(y, -1, 1), range_z=(z, -1, 1),
    slice=locations_for_vectors, 
    use_cm=False, 
    quiver_kw=quiver_kw
)
graphics(
    sphere_series[0],
    vector_field_3d(t_theta, **common_kws),
    vector_field_3d(t_phi, **common_kws),
    vector_field_3d(n, **common_kws),
    grid=False, backend=KB
)