Как ускорить ваш код 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)])
Это снова запускается в одно мгновение.Хотя это может и не быть существенным повышением скорости, это масштабируемо для огромных систем.