Числовая интеграция Python против Matlab - PullRequest
0 голосов
/ 26 февраля 2019

Мой код Python выполняется около 6,2 секунды.Код Matlab выполняется менее чем за 0,05 секунды.Почему это так и что я могу сделать, чтобы ускорить код Python?Является ли Cython решением?

Matlab:

function X=Test

nIter=1000000;
Step=.001;
X0=1;

X=zeros(1,nIter+1); X(1)=X0;

tic
for i=1:nIter
    X(i+1)=X(i)+Step*(X(i)^2*cos(i*Step+X(i)));
end
toc

figure(1) plot(0:nIter,X)

Python:

nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
x[0] = 1
start = time.time()
for i in range(1,1+nIter):
      x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
end = time.time()
print(end - start)

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

Как ускорить ваш код Python

Ваш самый большой временной интервал - np.cos, который выполняет несколько проверок формата ввода.Они актуальны и обычно незначительны для многомерных входов, но для вашего одномерного ввода это становится узким местом.Решение этой проблемы заключается в использовании math.cos, который принимает только одномерные числа в качестве входных данных и, следовательно, быстрее (хотя и менее гибким).

Другой приемник времени индексирует x несколько раз.Вы можете ускорить это, имея одну переменную состояния, которую вы обновляете, и записывая в x только один раз за итерацию.

При всем этом вы можете ускорить процесс примерно в десять раз:

import numpy as np
from math import cos

nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
state = x[0] = 1
for i in range(nIter):
    state += Step*state**2*cos(Step*i+state)
    x[i+1] = state

Теперь ваша главная проблема заключается в том, что ваш действительно внутренний цикл полностью происходит в Python, т. Е. У вас есть много операций обертывания, которые поглощают время.Вы можете избежать этого, используя uFuncs (например, созданные с помощью SymPy ufuncify) и используя NumPy accumulate:

import numpy as np
from sympy.utilities.autowrap import ufuncify
from sympy.abc import t,y
from sympy import cos

nIter = 1000000
Step = 0.001
state = x[0] = 1
f = ufuncify([y,t],y+Step*y**2*cos(t+y))

times = np.arange(0,nIter*Step,Step)
times[0] = 1
x = f.accumulate(times)

Это выполняется практически в одно мгновение.

… и почему это такне то, о чем вам следует беспокоиться

Если ваш точный код (и только он) - то, что вас волнует, то вам все равно не стоит беспокоиться о времени выполнения, потому что оно в любом случае очень короткое.Если, с другой стороны, вы используете это, чтобы измерить эффективность для проблем со значительным временем выполнения, ваш пример потерпит неудачу, потому что он учитывает только одно начальное условие и является очень простой динамикой.

Более того, вы используете Эйлерметод, который не очень эффективен или надежен, в зависимости от размера вашего шага.Последнее (Step) в вашем случае абсурдно мало, что дает гораздо больше данных, чем вам, вероятно, нужно: с размером шага 1, вы можете видеть, что происходит просто отлично.

Если вы хотите надежныйВ таких случаях почти всегда лучше использовать современный адаптивный интегратор, который может сам регулировать размер шага, например, вот решение вашей проблемы с использованием встроенного интегратора Python:

from math import cos
import numpy as np
from scipy.integrate import solve_ivp

T = 1000
dt = 0.001

x = solve_ivp(
        lambda t,state: state**2*cos(t+state),
        t_span = (0,T),
        t_eval = np.arange(0,T,dt),
        y0 = [1],
        rtol = 1e-5
    ).y

Это автоматическинастраивает размер шага на что-то большее, в зависимости от погрешности rtol.Он по-прежнему возвращает тот же объем выходных данных, но это происходит путем интерполяции решения.Для меня он длится 0,3 с.

Как ускорить процесс масштабируемым образом

Если вам все еще нужно ускорить что-то подобное, скорее всего, ваша производная (f)значительно сложнее, чем в вашем примере, и поэтому является узким местом.В зависимости от вашей проблемы, вы можете векторизовать его вычисление (используя NumPy или подобное).

Если вы не можете векторизовать, я написал модуль , который специально фокусируется на этом-Кодирование вашей производной под капотом.Вот ваш пример с шагом выборки 1.

import numpy as np
from jitcode import jitcode,y,t
from symengine import cos

T = 1000
dt = 1

ODE = jitcode([y(0)**2*cos(t+y(0))])
ODE.set_initial_value([1])
ODE.set_integrator("dop853")
x = np.hstack([ODE.integrate(t) for t in np.arange(0,T,dt)])

Это снова запускается в одно мгновение.Хотя это может и не быть существенным повышением скорости, это масштабируемо для огромных систем.

0 голосов
/ 26 февраля 2019

Разница в jit-компиляции, которую Matlab использует по умолчанию.Давайте попробуем ваш пример с Numba (джит-компилятор Python)

Код

import numba as nb
import numpy as np
import time

nIter = 1000000
Step = .001

@nb.njit()
def integrate(nIter,Step):
  x = np.zeros(1+nIter)
  x[0] = 1
  for i in range(1,1+nIter):
    x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
  return x

#Avoid measuring the compilation time,
#this would be also recommendable for Matlab to have a fair comparison
res=integrate(nIter,Step)

start = time.time()
for i in range(100):
  res=integrate(nIter,Step)

end=time.time()
print((end - start)/100)

Это приводит к времени выполнения 0,022 с на вызов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...