5

I was interested in integrating a vector field (i.e finding a streamline) for a given initial point using the scipy.integrate library. Since the vector field is a numpy.ndarray object, defined on a computational grid, the values in between the grid points have to be interpolated. Do any of the integrators handle this? That is, if I were to for instance attempt the following

import numpy as np
import scipy.integrate as sc
vx = np.random.randn(10,10)
vy = np.random.randn(10,10)
def f(x,t):
    return [vx[x[0],x[1]], vy[x[0],x[1]]] # which obviously does not work if x[i] is a float
p0 = (0.5,0.5)
dt = 0.1
t0 = 0
t1 = 1
t = np.arange(t0,t1+dt,dt)
sc.odeint(f,p0,t)

Edit :

I need to return the interpolated values of the vector field of the surrounding grid points :

def f(x,t):
    im1 = int(np.floor(x[0]))
    ip1 = int(np.ceil(x[1]))
    jm1 = int(np.floor(x[0]))
    jp1 = int(np.ceil(x[1]))
    if (im1 == ip1) and (jm1 == jp1):
        return [vx[x[0],x[1]], vy[x[0],x[1]]]
    else:
        points = (im1,jm1),(ip1,jm1),(im1,jp1),(ip1,jp1)
        values_x = vx[im1,jm1],vx[ip1,jm1],vx[im1,jp1],vx[ip1,jp1]
        values_y = vy[im1,jm1],vy[ip1,jm1],vy[im1,jp1],vy[ip1,jp1]
        return interpolated_values(points,values_x,values_y) # how ?

The last return statement is just some pseudo code. But this is basically what I am looking for.

Edit :

The scipy.interpolate.griddata function seem to be the way to go. Is it possible to incorporate it inside the function it self ? Something in the lines of this :

    def f(x,t):
        return [scipy.interpolate.griddata(x,vx),scipy.interpolate.griddata(x,vy)]
imranal
  • 656
  • 12
  • 35
  • I found the following in the scipy documentation(http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html#scipy.interpolate.griddata). This is exactly what I need. I just have to figure out how I can employ this with the scipy integrator, as it still requires a function. And I do not know what grid spacing the integrator uses. – imranal Nov 23 '15 at 07:07
  • I realize now that this question is better suited for Computational Science section. Is it possible to move it there ? Or shall I create a duplicate there ? – imranal Nov 23 '15 at 17:25
  • Your question is definitely on topic for StackOverflow - in fact you're probably much more likely to get a useful answer here, since SO has a large and active community of scipy users. – ali_m Nov 23 '15 at 20:22
  • How do you generate your vector field in your actual application? Doesn't that come from some form of 2D differential equation? In that case you could just integrate *that* to get a streamline... – thomas Nov 26 '15 at 13:57
  • I am generating my "vector fields" through eigendecomposition of second order tensor fields (which may, or may not be analytical). The vector field through the eigendecomposition becomes either the major or minor eigenvectors – imranal Nov 26 '15 at 14:00
  • Hopeful someone here can help me with a similar question [https://stackoverflow.com/questions/63220629/inverse-of-numpy-gradient-function] – Morgs Aug 05 '20 at 06:54

2 Answers2

5

I was going to suggest matplotlib.pyplot.streamplot which supports the keyword argument start_points as of version 1.5.0, however it's not practical and also very inaccurate.

Your code examples are a bit confusing to me: if you have vx, vy vector field coordinates, then you should have two meshes: x and y. Using these you can indeed use scipy.interpolate.griddata to obtain a smooth vector field for integration, however that seemed to eat up too much memory when I tried to do that. Here's a similar solution based on scipy.interpolate.interp2d:

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as interp
import scipy.integrate as integrate

#dummy input from the streamplot demo
y, x = np.mgrid[-3:3:100j, -3:3:100j]
vx = -1 - x**2 + y
vy = 1 + x - y**2

#dfun = lambda x,y: [interp.griddata((x,y),vx,np.array([[x,y]])), interp.griddata((x,y),vy,np.array([[x,y]]))]
dfunx = interp.interp2d(x[:],y[:],vx[:])
dfuny = interp.interp2d(x[:],y[:],vy[:])
dfun = lambda xy,t: [dfunx(xy[0],xy[1])[0], dfuny(xy[0],xy[1])[0]]

p0 = (0.5,0.5)
dt = 0.01
t0 = 0
t1 = 1
t = np.arange(t0,t1+dt,dt)

streamline=integrate.odeint(dfun,p0,t)

#plot it
plt.figure()
plt.plot(streamline[:,0],streamline[:,1])
plt.axis('equal')
mymask = (streamline[:,0].min()*0.9<=x) & (x<=streamline[:,0].max()*1.1) & (streamline[:,1].min()*0.9<=y) & (y<=streamline[:,1].max()*1.1)
plt.quiver(x[mymask],y[mymask],vx[mymask],vy[mymask])
plt.show()

Note that I made the integration mesh more dense for additional precision, but it didn't change much in this case.

Result:

output

Update

After some notes in comments I revisited my original griddata-based approach. The reason for this was that while interp2d computes an interpolant for the entire data grid, griddata only computes the interpolating value at the points given to it, so in case of a few points the latter should be much faster.

I fixed the bugs in my earlier griddata attempt and came up with

xyarr = np.array(zip(x.flatten(),y.flatten()))
dfun = lambda p,t: [interp.griddata(xyarr,vx.flatten(),np.array([p]))[0], interp.griddata(xyarr,vy.flatten(),np.array([p]))[0]]

which is compatible with odeint. It computes the interpolated values for each p point given to it by odeint. This solution doesn't consume excessive memory, however it takes much much longer to run with the above parameters. This is probably due to a lot of evaluations of dfun in odeint, much more than what would be evident from the 100 time points given to it as input.

However, the resulting streamline is much smoother than the one obtained with interp2d, even though both methods used the default linear interpolation method:

improved result

  • You can improve the integration by using the keyword argument `kind='cubic'` when performing interpolation. – imranal Nov 28 '15 at 18:21
  • There is one contention I have with your code, and that is that you are performing interpolation on the entire grid. If you want to generate streamlines for the entire vector field it would be understandable, but doing it for a single streamline makes it an unnecessary costly action. Would it be much more optimal to simply perform interpolation locally at each integration point? I.e at the closest grid points. – imranal Nov 28 '15 at 18:23
  • @imranal you're right: the simpler the interpolation, the easier the computation. However, 1. `help(interp.interp2d)` says `kind : {'linear', 'cubic', 'quintic'}` and the default is `linear`, which I made use of. So even if I wanted to, I couldn't use a simpler method. `interp.griddata` *does* support nearest-neighbour interpolation though, but I didn't try it. 2. I'm afraid that a nearest-neighbour interplation can lead to ugly results if your mesh is too dense, it might be bad to give a non-continuous vector field to `odeint`. A visual check could probably decide this. – Andras Deak -- Слава Україні Nov 28 '15 at 19:29
  • @imranal or maybe I misunderstood you. If you mean that we should use a *local* `linear`/`cubic` interpolation: I believe we already are. All of these interpolation methods involve a small neighbourhood of the interpolating point, so for a `cubic` interpolant you get a piece-wise cubic function. But the higher the order of the interpolant, the larger the neighbourhood one needs. That's actually the reason I let `python` use the default bi`linear` interpolator. From `help(interp.interp2d)`: `The minimum number of data points required [...] is (k+1)**2` with `k=1,3,5` for linear,cubic,quintic. – Andras Deak -- Слава Україні Nov 28 '15 at 19:31
  • @imranal I added an update based on `griddata`: it's ultimately much slower (due to multiple function evaluations), but the result is much smoother. – Andras Deak -- Слава Україні Nov 28 '15 at 20:20
  • Regarding your update : You need to flatten the numpy arrays vx and vy only once. I ran some time on the difference between the interpolation methods you have listed. And it turns out that for less dense grids, `interp2d` (with cubic method) is faster than `griddata`, while for dense grids the opposite is the case. But in general, we are speaking of relatively sparse grids I tested this on (ranging from 10x10 to 50x50), and they were quite demanding still. This makes both interpolation techniques useless for larger grids, say 500x500. Especially if integration is performed multiple times. – imranal Nov 28 '15 at 23:51
  • I have added your amended code here (as unlisted just in case) : http://pastebin.com/7uGCjfSn – imranal Nov 28 '15 at 23:52
  • @imranal Thanks, you're right about flattening, of course. So do you really need the time-integral of the vector field, or just the streamline up to some point? Wouldn't it be an option to implement the integration manually, like using some Euler method? Then you'd have a close grip on the number of computations involved, but of course if your phase lines are messy, then a simpler integration can be less accurate. And thanks for the amended code. – Andras Deak -- Слава Україні Nov 29 '15 at 00:12
3

In case of someone have an expression of the field, I have used a concise version of Andras answer without the mask and vectors:

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import ode

vx = lambda x,y: -1 - x**2 + y
vy = lambda x,y: 1 + x - y**2

y0, x0 = 0.5, 0.6

def f(x, y):
    return vy(x,y)/vx(x,y)

r = ode(f).set_integrator('vode', method='adams')
r.set_initial_value(y0, x0)

xf =  1.0
dx = -0.001

x, y = [x0,], [y0,]
while r.successful() and r.t <= xf:
    r.integrate(r.t + dx)
    x.append(r.t + dx)
    y.append(r.y[0])

#plot it
plt.figure()
plt.plot(x, y)
plt.axis('equal')
plt.show()

enter image description here

I hope it is useful for someone with the same needs.