Visualizing Gradients

Author: Robert McNees (rmcnees@luc.edu)
Date: March 1, 2021

Note: A recent (version 12.2.0.0) bug in SliceContourPlot3D and SliceDensityPlot3D affects this notebook. Be sure to evaluate the Clear[x,y,z] commands indicated below!

First, let's define a function of two variables: x and y.

f[x_,y_]:= -Cos[x]+Cos[y]

Now let’s produce a 3-D plot of this function to get a feel for its shape. I’m adding a few optional instructions to the Plot3D command to change the color scheme and label the axes.

Plot3D[f[x,y],{x,-4,4},{y,-4,4},ColorFunction->"Rainbow",AxesLabel->{"x","y","f(x,y)"}]

When we plot the function this way, with its value along the vertical axis, it looks like a topographical map. A good way to think of it is that the function increases as we go uphill, and decreases as we go downhill.

Now let' s produce a “contour plot.” Think of it as a bird’s eye view of the function with a bunch of curves superimposed that show constant values of the function.

ContourPlot[f[x,y],{x,-4,4},{y,-4,4},Contours->20,ColorFunction->"Rainbow",ContourStyle->Directive[{Thickness[.002]}]]

The colors here correspond to the colors on our first plot. Reddish regions are close to the function’s maximum value (2, in this case) and purplish regions are close to the function’s minimum value.

Now move your cursor over the plot. As it passes over each contour a tooltip pops up showing you the corresponding value of the function. That’s the value that the function takes at every point on that curve.

Just to make sure this is clear, let’s place the 3D plot above the contour plot in the same image. That way we can identify a spot on the 3D plot and make sure we understand what it corresponds to on the contour plot. Don’t worry about the details here; just focus on the plot below.

Clear[x,y]

contour=SliceContourPlot3D[f[x,y],z==-3,{x,-4,4},{y,-4,4},{z,-3,3},Contours->20,PlotPoints-> 100,ColorFunction->"Rainbow",ContourStyle->Directive[{Thickness[.002]}]];

threed=Plot3D[f[x,y],{x,-4,4},{y,-4,4},ColorFunction->"Rainbow",PlotPoints-> 100,AxesLabel->{"x","y","f(x,y)"}];

Show[contour,threed]

Important: There appears to be a bug in the SliceContourPlot3D function in Mathematica v12 which assigns the last-used plot values to x, y, and z . Run the following command to clear them or the rest of the notebook won’t work.

Clear[x,y,z]

Next, let’s evaluate the gradient of our function. First we’ll do it by hand. The gradient is a vector, and in Mathematica a vector is a list. So let’s make a list with df/dx as its first entry (the x-component) and df/dy as its second entry (the y-component).

{D[f[x,y],x],D[f[x,y],y]}

Or, we could also use Mathematica’s built-in “Grad” command.

?Grad

Grad[f[x,y],{x,y}]

We' re going to use the gradient of this function extensively, so let’s define a vector function that we’ll call “gradf[x,y].” That way when we need the gradient at a particular point we can just use this function at that point.

gradf[x_,y_]:={Sin[x],-Sin[y]}

Here' s the gradient at the point x=1, y=2.

gradf[1,2]//N

Like every vector, the gradient has both a magnitude and a direction. Today we want to focus on the direction part, to verify what I said in class -- that the gradient always points us in the direction that gives the biggest change in the function. To get the unit vector associated with the direction of the gradient we just divide it by its magnitude.

dir[x_,y_]:= ((gradf[x,y])/(Sqrt[gradf[x,y].gradf[x,y]]))

Be careful! At points (x,y) where sin(x) = 0 and sin(y) = 0, the gradient vanishes. That means the numerator and denominator in dir[x,y] are both zero and the expression is indeterminate. We’d have to be more careful with our code if we wanted to account for this. I’m going to gloss over this point to keep the code simple, and just be careful not to visit any of those points in my calculations below.

What I want to do now is draw some arrows on our contour plot that show the direction of the gradient at different points. Based on our discussion in class the arrows should always point in the “steepest” or “most uphill” direction.

First I'm going to define a command to help me draw a dot at an (x,y) point with an arrow attached in the direction of the gradient. Again, don’t worry about the code -- just focus on the results below.

gradarrow[x_,y_]:= {Thickness[.002],Disk[{x,y},0.05],Arrowheads[0.03],Arrow[{{x,y},{x,y}+0.6dir[x,y]}]}

Now we’ll make another contour plot with some of our gradient arrows added at various points:

ContourPlot[f[x,y],{x,-4,4},{y,-4,4},Contours->10,AspectRatio->1,ColorFunction->"Rainbow",Epilog-> {gradarrow[1,1],gradarrow[1,0],gradarrow[-2,1],gradarrow[2.5,-2],gradarrow[-2.5,-1]},PlotPoints->100]

The direction of the gradient tells us which way to go to see the biggest change in our function. In this plot, that means “towards the reddish area” at each point. And indeed, each of these arrows is pointing “uphill” in the direction that gives us the biggest change in our function.

Here's the same plot with many more arrows shown. I have also increased the number of contours.

ContourPlot[f[x,y],{x,-4,4},{y,-4,4},Contours->28,AspectRatio->1,ColorFunction->"Rainbow",Epilog-> Table[gradarrow[x,y],{x,-3.5,3.5,1.0},{y,-3.5,3.5,1.0}],PlotPoints->100]

Finally, let’s combine what we just did (the arrows) with that useful plot that showed both the contours and the 3D rendering of the function. Again, don’t worry about this next bit of code, which is just a little routine for drawing a sphere at an (x,y,z) point, with a little arrow in the x-y plane showing the direction of the gradient.

gradarrow3d[x_,y_,z_,l_]:= {Thickness[.002],Black,Sphere[{x,y,z},0.025],Arrowheads[0.015],Arrow[{{x,y,z},{x,y,z}+l*{dir[x,y][[1]],dir[x,y][[2]],0}}]}

The "Show" command takes a bunch of different graphics objects and...shows them.

Show[SliceContourPlot3D[f[x,y],z==-3,{x,-4,4},{y,-4,4},{z,-3,3},Contours->20,PlotPoints-> 100,ColorFunction->"Rainbow",ContourStyle->Directive[{Thickness[.002]}],PlotPoints->100],threed,Graphics3D[{gradarrow3d[1,1,-2.9,.8],gradarrow3d[1,0,-2.9,.8],gradarrow3d[-2,1,-2.9,.8],gradarrow3d[2.5,-2,-2.9,.8],gradarrow3d[-2.5,-1,-2.9,.8]}]]

Important: Run the following command to clear x, y, and z because of the bug in SliceContourPlot3D.

Clear[x,y,z]

finalplot=Show[SliceContourPlot3D[f[x,y],z==-3,{x,-4,4},{y,-4,4},{z,-3,3},Contours->20,PlotPoints-> 100,ColorFunction->"Rainbow",ContourStyle->Directive[{Thickness[.002]}],PlotPoints->100],threed,Graphics3D[Table[gradarrow3d[x,y,-2.9,.6],{x,-3.5,3.5,1.0},{y,-3.5,3.5,1.0}]],FaceGrids-> {Front,Left},ImageMargins->20,ImagePadding-> 40,ImageSize-> Large]

Export["~/gradient.png",finalplot,ImageResolution-> 500]

Export["~/gradient.pdf",finalplot]