Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

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):
    r"""
    Plot gradient vectors of a bivariate function on points of its graph.
    Arguments:
    - 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
    Options:
    - plot_points: number of points in each direction; it can be a number or a list or a tuple
    - 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

    Example:
    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)
    show(g+s)

    Another example:
    var("x,y")
    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)
    show(surface+gradients)
    """

    # 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
    else:
        nx = ny = plot_points

    # color
    if color=="gradient":
        color = "red"
        gradient_colors = True
    else:
        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()
            norms.append(nv)
            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]
            arrows[i].set_texture(color=vcol)
    # End
    return sum(arrows)

image description

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):
    r"""
    Plot gradient vectors of a bivariate function on points of its graph.
    Arguments:
    - 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
    Options:
    - plot_points: number of points in each direction; it can be a number or a list or a  tuple
    - color: a specific color for all vectors or "gradient"; in this case, vectors are colored
      
      colored depending on their norms
    - radius: radius of the arrow
    - scale: a factor to increase or decrease the length of the arrows

    Example:
    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)
    show(g+s)

    Another example:
    var("x,y")
    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)
    show(surface+gradients)
    """

    # 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
    else:
        nx = ny = plot_points

    # color
    if color=="gradient":
        color = "red"
        gradient_colors = True
    else:
        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()
            norms.append(nv)
            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]
            arrows[i].set_texture(color=vcol)
    # End
    return sum(arrows)

image description

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):
    r"""
    Plot gradient vectors of a bivariate function on points of its graph.
    Arguments:
    - 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 
of
      fun should be the first elements of these tuples
    Options:
    - plot_points: number of points in each direction; it can be a number or a list or a 
      tuple
    - 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

    Example:
    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)
    show(g+s)

    Another example:
    var("x,y")
    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)
    show(surface+gradients)
    """

    # 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
    else:
        nx = ny = plot_points

    # color
    if color=="gradient":
        color = "red"
        gradient_colors = True
    else:
        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()
            norms.append(nv)
            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]
            arrows[i].set_texture(color=vcol)
    # End
    return sum(arrows)

image description

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):
    r"""
    Plot gradient vectors of a bivariate function on points of its graph.
    Arguments:
    - 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
    Options:
    - plot_points: number of points in each direction; it can be a number or a list or a 
      tuple
    - 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

    Example:
    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)
    show(g+s)

    Another example:
    var("x,y")
    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)
    show(surface+gradients)
    """

    # 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
    else:
        nx = ny = plot_points

    # color
    if color=="gradient":
        color = "red"
        gradient_colors = True
    else:
        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()
            norms.append(nv)
            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]
            arrows[i].set_texture(color=vcol)
    # 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

by

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