16

I am trying to draw a potential field for a given object using the following formula:

U=-α_goal*e^(-((x-x_goal )^2/a_goal +(y-y_goal^2)/b_goal ) )

using the following code

    # Set limits and number of points in grid
    xmax = 10.0
    xmin = -xmax
    NX = 20
    ymax = 10.0
    ymin = -ymax
    NY = 20
    # Make grid and calculate vector components
    x = linspace(xmin, xmax, NX)
    y = linspace(ymin, ymax, NY)
    X, Y = meshgrid(x, y)
    x_obstacle = 0
    y_obstacle = 0
    alpha_obstacle = 1
    a_obstacle = 1
    b_obstacle = 1
    P = -alpha_obstacle * exp(-(X - x_obstacle)**2 / a_obstacle + (Y - y_obstacle)**2 / b_obstacle)
    Ey,Ex = gradient(P)
    print Ey
    print Ex

    QP = quiver(X, Y, Ex, Ey)

    show()

This code calculates a potential field. How can I plot this potential field nicely? Also, given a potential field, what is the best way to convert it to a vector field? (vector field is the minus gradient of the potential field. )

I would appreciate any help.

I have tried using np.gradient() but the result is not what I have expected:

enter image description here

What I do expect, is something along these lines: enter image description here

EDIT: After changing the two lines in the code:

y, x = np.mgrid[500:-100:200j, 1000:-100:200j] 
p = -1 * np.exp(-((x - 893.6)**2 / 1000 + (y - 417.35)**2 / 1000))

I have an incorrect plot: it seems to be inverted left-right (arrows seem to be in correct spot but not the field):enter image description here EDIT: Fixed by changing to y, x = np.mgrid[500:-100:200j, -100:1000:200j] Any idea why?

Tad
  • 838
  • 2
  • 11
  • 22
  • It sounds like you want a [matplotlib quiver plot](http://matplotlib.org/examples/pylab_examples/quiver_demo.html) – Cory Kramer Aug 16 '14 at 17:26
  • Yes, I have looked at the quiver function. Given my formula for P, how do I get U and V from http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.quiver ?I think my formula just gives me a potential value at each location and not a vector. Am I missing something? – Tad Aug 16 '14 at 17:32
  • 1
    @Tad - Use `numpy.gradient` to get the gradient of the potential field. E.g. `v, u = np.gradient(P)` in your example above. (It returns dy, dx rather than dx, dy because numpy arrays are indexed row, column rather than column, row.) – Joe Kington Aug 16 '14 at 18:46
  • Thank you Joe, I did try that method - I have updated my post to show what I obtained. Could you let me know what I am doing incorrectly? – Tad Aug 16 '14 at 18:59

1 Answers1

47

First off, let's evaluate it on a regular grid, similar to your example code. (On a side note, you have an error in the code to evaluate your equation. It's missing a negative inside the exp.):

import numpy as np
import matplotlib.pyplot as plt

# Set limits and number of points in grid
y, x = np.mgrid[10:-10:100j, 10:-10:100j]

x_obstacle, y_obstacle = 0.0, 0.0
alpha_obstacle, a_obstacle, b_obstacle = 1.0, 1e3, 2e3

p = -alpha_obstacle * np.exp(-((x - x_obstacle)**2 / a_obstacle
                               + (y - y_obstacle)**2 / b_obstacle))

Next, we'll need to calculate the gradient (this is a simple finite-difference, as opposed to analytically calculating the derivative of the function above):

# For the absolute values of "dx" and "dy" to mean anything, we'll need to
# specify the "cellsize" of our grid.  For purely visual purposes, though,
# we could get away with just "dy, dx = np.gradient(p)".
dy, dx = np.gradient(p, np.diff(y[:2, 0]), np.diff(x[0, :2]))

Now we can make a "quiver" plot, however, the results probably won't be quite what you'd expect, as an arrow is being displayed at every point on the grid:

fig, ax = plt.subplots()
ax.quiver(x, y, dx, dy, p)
ax.set(aspect=1, title='Quiver Plot')
plt.show()

enter image description here

Let's make the arrows bigger. The easiest way to do this is to plot every n-th arrow and let matplotlib handle the autoscaling. We'll use every 3rd point here. If you want fewer, larger arrows, change the 3 to a larger integer number.

# Every 3rd point in each direction.
skip = (slice(None, None, 3), slice(None, None, 3))

fig, ax = plt.subplots()
ax.quiver(x[skip], y[skip], dx[skip], dy[skip], p[skip])
ax.set(aspect=1, title='Quiver Plot')
plt.show()

enter image description here

Better, but those arrows are still pretty hard to see. A better way to visualize this might be with an image plot with black gradient arrows overlayed:

skip = (slice(None, None, 3), slice(None, None, 3))

fig, ax = plt.subplots()
im = ax.imshow(p, extent=[x.min(), x.max(), y.min(), y.max()])
ax.quiver(x[skip], y[skip], dx[skip], dy[skip])

fig.colorbar(im)
ax.set(aspect=1, title='Quiver Plot')
plt.show()

enter image description here

Ideally, we'd want to use a different colormap or change the arrow colors. I'll leave that part to you. You might also consider a contour plot (ax.contour(x, y, p)) or a streamplot (ax.streamplot(x, y, dx, dy). Just to show a quick example of those:

fig, ax = plt.subplots()

ax.streamplot(x, y, dx, dy, color=p, density=0.5, cmap='gist_earth')

cont = ax.contour(x, y, p, cmap='gist_earth')
ax.clabel(cont)

ax.set(aspect=1, title='Streamplot with contours')
plt.show()

enter image description here

...And just for the sake of getting really fancy:

from matplotlib.patheffects import withStroke

fig, ax = plt.subplots()

ax.streamplot(x, y, dx, dy, linewidth=500*np.hypot(dx, dy),
              color=p, density=1.2, cmap='gist_earth')

cont = ax.contour(x, y, p, cmap='gist_earth', vmin=p.min(), vmax=p.max())
labels = ax.clabel(cont)

plt.setp(labels, path_effects=[withStroke(linewidth=8, foreground='w')])

ax.set(aspect=1, title='Streamplot with contours')
plt.show()

enter image description here

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • wow, this is great! I will look closely at your code but wanted to thank you right now for your great reply! – Tad Aug 16 '14 at 19:28
  • Happy to help! If anything isn't clear or doesn't work, feel free to ask. – Joe Kington Aug 16 '14 at 19:36
  • 2
    Careful with `numpy.gradient` -- the default step size is 1, so it's in the above example the result is not correctly scaled. – pv. Aug 17 '14 at 14:09
  • @pv - Good point! It won't have any visual effect, in this case, but the absolute values of `dx` and `dy` are completely wrong, as you pointed out. Updated the answer to use the step size. Thanks! – Joe Kington Aug 17 '14 at 14:48
  • Hey Joe, I have made a small change to the code: `y, x = np.mgrid[500:-100:200j, 1000:-100:200j]` `p = -1 * np.exp(-((x - 893.6)**2 / 1000 + (y - 417.35)**2 / 1000))` aka I have changed the limits and the number of points and drew a sample field. My result is weird - arrows seem to come from the correct position but the colormap seems to be centered at wrong x (15 instead of 893). Do you know what might cause this? The rest of the lines are unchanged – Tad Aug 17 '14 at 18:24
  • I have added a picture above showing the incorrect plot. It seems it is inverted left-right – Tad Aug 17 '14 at 18:31
  • What seems to have fixed it was `y, x = np.mgrid[500:-100:200j, -100:1000:200j]` Any idea why? – Tad Aug 17 '14 at 21:52
  • @JoeKington do you know how I can draw a vector on top of a potential field drawn above? i.e. after I do quiver which draws the field, I would like to add few more vectors in a way which does not break autoscaling. Thanks! – Tad Aug 24 '14 at 15:46
  • Note that the streamplot will only work if the points are equally spaces along the axes, it seems. – Dr_Zaszuś Dec 14 '20 at 09:28