Интеграция векторного поля (массив numpy) с использованием scipy.integrate

Мне было интересно интегрировать векторное поле (то есть найти линию тока) для заданной начальной точки с использованием библиотеки scipy.integrate. Поскольку векторное поле представляет собой объект numpy.ndarray, определенный на вычислительной сетке, значения между точками сетки необходимо интерполировать. Кто-нибудь из интеграторов справляется с этим? То есть, если бы я, например, попытался выполнить следующее

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)

Изменить :

Мне нужно вернуть интерполированные значения векторного поля окружающих точек сетки:

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 ?

Последний оператор return — это просто какой-то псевдокод. Но это в основном то, что я ищу.

Изменить :

scipy.interpolate.griddata< Функция /a> кажется подходящей. Можно ли включить его в саму функцию? Что-то в строках этого:

    def f(x,t):
        return [scipy.interpolate.griddata(x,vx),scipy.interpolate.griddata(x,vy)]

person imranal    schedule 22.11.2015    source источник
comment
В документации по scipy я нашел следующее (docs.scipy.org/doc/scipy/reference/generated/). Это именно то, что мне нужно. Мне просто нужно выяснить, как я могу использовать это с интегратором scipy, так как для этого все еще требуется функция. И я не знаю, какой шаг сетки использует интегратор.   -  person imranal    schedule 23.11.2015
comment
Теперь я понимаю, что этот вопрос лучше подходит для раздела вычислительной науки. Можно ли его туда перенести? Или мне создать там дубликат?   -  person imranal    schedule 23.11.2015
comment
Ваш вопрос определенно относится к теме StackOverflow - на самом деле у вас, вероятно, гораздо больше шансов получить здесь полезный ответ, поскольку у SO есть большое и активное сообщество пользователей scipy.   -  person ali_m    schedule 23.11.2015
comment
Как вы создаете свое векторное поле в своем реальном приложении? Разве это не происходит из какой-то формы двумерного дифференциального уравнения? В этом случае вы можете просто интегрировать это, чтобы получить оптимизацию...   -  person thomas    schedule 26.11.2015
comment
Я генерирую свои векторные поля посредством собственного разложения тензорных полей второго порядка (которые могут быть, а могут и не быть аналитическими). Векторное поле через собственное разложение становится либо главным, либо второстепенным собственным вектором.   -  person imranal    schedule 26.11.2015
comment
Надеюсь, кто-то здесь может помочь мне с аналогичным вопросом [stackoverflow.com/questions/63220629/   -  person Morgs    schedule 05.08.2020


Ответы (2)


Я собирался предложить matplotlib.pyplot.streamplot, который поддерживает аргумент ключевого слова start_points по состоянию на версия 1.5.0, однако она непрактична и очень неточна.

Ваши примеры кода меня немного сбивают с толку: если у вас есть vx, vy координаты векторного поля, то у вас должно быть две сетки: x и y. Используя их, вы действительно можете использовать scipy.interpolate.griddata для получения гладкого векторного поля для интегрирования, однако, когда я пытался это сделать, казалось, что это занимает слишком много памяти. Вот похожее решение на основе 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()

Обратите внимание, что я сделал сетку интегрирования более плотной для большей точности, но в данном случае это мало что изменило.

Результат:

выход

Обновлять

После некоторых заметок в комментариях я пересмотрел свой первоначальный подход, основанный на griddata. Причина этого заключалась в том, что в то время как interp2d вычисляет интерполятор для всей сетки данных, griddata вычисляет значение интерполяции только в заданных ему точках, поэтому в случае нескольких точек последнее должно быть намного быстрее.

Я исправил ошибки в своей предыдущей попытке griddata и придумал

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]]

который совместим с odeint. Он вычисляет интерполированные значения для каждой точки p, предоставленной ему odeint. Это решение не потребляет лишнего объема памяти, однако его работа с указанными выше параметрами занимает намного больше времени. Это, вероятно, связано с большим количеством оценок dfun в odeint, намного больше, чем было бы очевидно из 100 моментов времени, заданных для него в качестве входных данных.

Однако результирующая линия тока намного более гладкая, чем линия, полученная с помощью interp2d, даже несмотря на то, что оба метода использовали метод интерполяции linear по умолчанию:

улучшенный результат

person Andras Deak    schedule 28.11.2015
comment
Вы можете улучшить интеграцию, используя аргумент ключевого слова kind='cubic' при выполнении интерполяции. - person imranal; 28.11.2015
comment
У меня есть один спор с вашим кодом, и он заключается в том, что вы выполняете интерполяцию по всей сетке. Если вы хотите сгенерировать линии тока для всего векторного поля, это было бы понятно, но выполнение этого для одной линии тока делает это ненужным дорогостоящим действием. Было бы гораздо оптимальнее просто выполнять интерполяцию локально в каждой точке интегрирования? То есть в ближайших точках сетки. - person imranal; 28.11.2015
comment
@imranal вы правы: чем проще интерполяция, тем проще вычисления. Однако 1. help(interp.interp2d) говорит kind : {'linear', 'cubic', 'quintic'}, а по умолчанию linear, что я и использовал. Поэтому, даже если бы я хотел, я не мог бы использовать более простой метод. interp.griddata поддерживает интерполяцию ближайшего соседа, но я не пробовал. 2. Я боюсь, что интерполяция ближайшего соседа может привести к некрасивым результатам, если ваша сетка слишком плотная, было бы плохо задавать прерывистое векторное поле для odeint. Визуальная проверка, вероятно, могла бы решить это. - person Andras Deak; 28.11.2015
comment
@imranal или, может быть, я вас неправильно понял. Если вы имеете в виду, что мы должны использовать интерполяцию local linear/cubic: я думаю, что мы уже используем. Все эти методы интерполяции включают небольшую окрестность точки интерполяции, поэтому для интерполянта cubic вы получаете кусочно-кубическую функцию. Но чем выше порядок интерполянта, тем больше нужная окрестность. Именно по этой причине я позволил python использовать интерполятор bilinear по умолчанию. От help(interp.interp2d): The minimum number of data points required [...] is (k+1)**2 с k=1,3,5 для линейного, кубического, пятого. - person Andras Deak; 28.11.2015
comment
@imranal Я добавил обновление на основе griddata: в конечном итоге оно намного медленнее (из-за множественных вычислений функций), но результат намного более плавный. - person Andras Deak; 28.11.2015
comment
Что касается вашего обновления: вам нужно сгладить массивы numpy vx и vy только один раз. Я некоторое время изучал разницу между перечисленными вами методами интерполяции. И получается, что для менее плотных сеток interp2d (при кубическом методе) быстрее, чем griddata, а для плотных сеток все наоборот. Но в целом, мы говорим об относительно разреженных сетках, на которых я это тестировал (от 10x10 до 50x50), и они все еще были довольно требовательными. Это делает оба метода интерполяции бесполезными для больших сеток, скажем, 500x500. Особенно, если интеграция выполняется несколько раз. - person imranal; 29.11.2015
comment
Я добавил ваш измененный код сюда (на всякий случай не указан): pastebin.com/7uGCjfSn - person imranal; 29.11.2015
comment
@imranal Спасибо, конечно, вы правы насчет выравнивания. Так вам действительно нужен интеграл по времени от векторного поля или просто линия тока до какой-то точки? Не было бы возможности реализовать интеграцию вручную, например, используя какой-нибудь метод Эйлера? Тогда у вас будет четкий контроль над количеством задействованных вычислений, но, конечно, если ваши фазовые линии беспорядочны, то более простое интегрирование может быть менее точным. И спасибо за измененный код. - person Andras Deak; 29.11.2015

Если у кого-то есть выражение поля, я использовал краткую версию ответа Андраса без маски и векторов:

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()

введите здесь описание изображения

Я надеюсь, что это полезно для кого-то с такими же потребностями.

person Marcos Lourenço    schedule 12.08.2018
comment
Спасибо @Svaberg за обнаружение недостатка. Теперь он должен работать правильно. - person Marcos Lourenço; 31.07.2020
comment
Я только что заметил этот ответ! Хороший улов, я думаю, что действительно полезно иметь версию, которая использует объектно-ориентированный интегратор с отслеживанием состояния. - person Andras Deak; 31.07.2020