Ask Your Question

plotting a Surface gradient

asked 2020-03-02 11:44:08 -0500

I'm looking to plot the surface gradient vector field

$$\left \langle \frac{f_x}{\|\nabla f\|}, \frac{f_y}{\|\nabla f\|}, \|\nabla f\| \right \rangle$$

but only at points on the the surface $(x,y,f(x,y)$.

plotvectorfield3d will plot this field everywhere in 3D. I want to restrict this to the surface only.

Right now, I can plot these vectors individually by iterating along the surface, but I am wondering if someone has already tackled this more efficiently, perhaps with parameters on how to iterate.

Further, it would be nice to generalize this to directional derivatives for any unit vector $\mathbf{u} = \left \langle a, b \right \rangle$.

edit retag flag offensive close merge delete

1 answer

Sort by ยป oldest newest most voted

answered 2020-03-04 20:54:05 -0500

Juanjo gravatar image

updated 2020-03-05 09:05:38 -0500

I think that SageMath does not have a function behaving as you want, but you can define your own. In fact, it seems that this is what you are doing. On my side, I have ended with the following code which perhaps may be useful. It can be seem, as well as the examples, in this SageMath Cell. The image at the end corresponds to the second example.

def gradient_plot(fun, range_x, range_y, plot_points=10, color="red", radius=1/50, scale=1):
    Plot gradient vectors of a bivariate function on points of its graph.
    - fun: a symbolic expression corresponding to the function
    - range_x and range_y: tuples of the form (var, min_value, max_value); the variables of
      fun should be the first elements of these tuples
    - plot_points: number of points in each direction; it can be a number or a list or a 
    - color: a specific color for all vectors or "gradient"; in this case, vectors are 
      colored depending on their norms
    - radius: radius of the arrow
    - scale: a factor to increase or decrease the length of the arrows

    f(u,v) = u^2-v^2
    g = gradient_plot(f(u,v), (u,-1,1), (v,-1,1), scale=0.5)
    s = plot3d(f(u,v), (u,-1,1), (v,-1,1), mesh=True)

    Another example:
    fun = cos(x+y)*sin(x-y)
    x1, x2, y1, y2 = 0, pi, 0, pi
    surface = plot3d(fun, (x,x1,x2), (y,y1,y2), mesh=True, color="green", plot_points=21)
    gradients = gradient_plot(fun, (x,x1,x2), (y,y1,y2), plot_points=11, 
                                scale=0.5, color="gradient", radius=0.02)

    # Process arguments
    # -----------------
    # fun
    fx, fy = fun.gradient()

    # ranges
    var_x, xmin, xmax = range_x
    var_y, ymin, ymax = range_y

    # plot_points
    if isinstance(plot_points, (list, tuple)):
        nx, ny = plot_points
        nx = ny = plot_points

    # color
    if color=="gradient":
        color = "red"
        gradient_colors = True
        gradient_colors = False    

    # Compute gradient vectors at surface points and obtain the
    # corresponding arrows to be plotted
    # Gradient norms are also stored
    norms = []
    arrows = []
    dx, dy = (xmax-xmin)/(nx-1), (ymax-ymin)/(ny-1)
    iter_x = [xmin,xmin+dx,..,xmax]
    iter_y = [ymin,ymin+dy,..,ymax]
    for xx in iter_x:
        for yy in iter_y:
            dic = {var_x: xx, var_y: yy}
            p = [xx, yy, fun(dic)]
            v = vector([-fx(dic), -fy(dic), 1])
            nv = norm(v).n()
            v = scale*v/nv
            arrows.append(arrow((0,0,0),v, color=color, radius=radius).translate(p))

    # If required, modify arrow colors depending on the gradient norm.
    # Colors range from dark blue (small norm) to dark red (big norm)
    if gradient_colors:
        nvmin, nvmax = min(norms), max(norms)
        if nvmin==nvmax: nvmin = 0
        for i in range(len(arrows)):
            icol = (norms[i]-nvmin)/(nvmax-nvmin)
            vcol = colormaps.jet(icol)[:-1]
    # End
    return sum(arrows)

image description

Edit. The line fx, fy = fun.gradient() fails if fun is a constant or only contains one variable. So it is better to replace

fx, fy = fun.gradient()
var_x, xmin, xmax = range_x
var_y, ymin, ymax = range_y


var_x, xmin, xmax = range_x
var_y, ymin, ymax = range_y
fx, fy = fun.diff(var_x), fun.diff(var_y)
edit flag offensive delete link more


Thank you for this! What a wonderful template. I was looking to do vectors tangent to the surface in the direction of the gradient -- I modified your code slightly (here) and it looks beautiful!

v = vector([fx(dic), fy(dic), ((fx(dic))^2+(fy(dic))^2)])

and then changed it to negative gradient descent (to simulate gravity along the surface)

v = scale*v/nv*-1

Greatly appreciated. I have also playing with embedding (curves in 3D onto a surface) -- giving the curves some "thickness" is an interesting exercise, working on automating it, along with generating vector fields on it as above.

Abbas Jaffary gravatar imageAbbas Jaffary ( 2020-05-17 13:09:13 -0500 )edit

I've seen your tweet and the associated SageMath cell. Very nice picture!

Juanjo gravatar imageJuanjo ( 2020-06-07 17:54:59 -0500 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools


Asked: 2020-03-02 11:26:45 -0500

Seen: 102 times

Last updated: Mar 05