Основная математическая проблема
Формула для 1-й производной, которую использует np.gradient
, имеет ошибку O(h**2)
(где h - ваш dx). Поскольку вы продолжаете принимать деривативы, это может быть плохо, поскольку изменение функции на величину z
может изменить ее дериват на z/h
. Обычно этого не происходит, потому что ошибка исходит от производных функции более высокого порядка, которые сами изменяются плавно; поэтому последующее дифференцирование «дифференцирует» большую часть ошибки предыдущего шага, а не увеличивает ее на 1/h
.
Тем не менее, у границы другая история, где мы должны перейти от одной формулы конечных разностей (центрированная разница) к другой. Формула границы также имеет ошибку O(h**2)
, но она отличается O(h**2)
. И теперь у нас есть проблема с последующими этапами дифференциации, каждый из которых может дать коэффициент 1/h
. Наихудший сценарий для 4-й производной - O(h**2) * (1/h**3)
, который, к счастью, здесь не реализуется, но вы все равно получаете довольно плохой граничный эффект. Я предлагаю два разных решения, которые работают примерно одинаково (второе несколько лучше, но дороже). Но сначала давайте подчеркнем значение того, что Уоррен Векессер сказал в комментарии:
Если функция периодическая , вы расширяете ее по периодичности, например, np.tile(Y, 3)
, вычисляете производную от по и берете среднюю часть, обрезая любые граничные эффекты ,
Прямое вычисление 4-й производной
Вместо того чтобы применять формулу конечных разностей для 1-й производной четыре раза, примените ее для 4-й производной один раз. Все еще будет проблема граничных значений, для которой моя лучшая идея - самая простая, постоянная экстраполяция. То есть:
Y_xxxx = (Y[4:] - 4*Y[3:-1] + 6*Y[2:-2] - 4*Y[1:-3] + Y[:-4])/(dx**4)
Y_xxxx = np.concatenate((2*[Y_xxxx[0]], Y_xxxx, 2*[Y_xxxx[-1]]))
с результатом
Если вам не нравятся маленькие плоские кусочки сбоку, возьмите меньшее dx
(они имеют размер 2*dx
).
Дифференцировать интерполирующий сплайн
Установите сплайн 5-й степени для данных, затем возьмите его 4-ю производную аналитически . Это требует SciPy и, вероятно, намного медленнее, чем прямой подход.
from scipy.interpolate import InterpolatedUnivariateSpline
spl = InterpolatedUnivariateSpline(X, Y, k=5)
Y_xxxx = spl.derivative(4)(X)
Результат:
Неоднородная сетка
Потеря точности на границах типична для интерполяции на равномерной сетке, поэтому мы должны ожидать улучшения, используя вместо этого чебышевские узлы. Вот оно:
def cheb_nodes(a, b, N):
jj = 2.*np.arange(N) + 1
x = np.cos(np.pi * jj / 2 / N)[::-1]
x = (a + b + (b - a)*x)/2
return x
N = 64
X = cheb_nodes(0, 1, N)
Остальное продолжается, как и раньше, используя InterpolatedUnivariateSpline
степени 5. График самого 4-го производного теперь не показывает видимой разницы между числами и аналитикой, поэтому вместо этого приведен график Y_xxxx - analytics
:
Ошибка не более 3, и она больше не максимальна на границе. Максимальная ошибка с равномерной сеткой была около 33.
Я также исследовал возможность наложения фиксированного состояния на интерполирующий сплайн для дальнейшего повышения его точности. Возможно, make_interp_spline
может сделать это с
l, r = [(0, 0), (1, 0)], [(0, 0), (1, 0)]
spl = make_interp_spline(X, Y, k=5, bc_type=(l, r))
но есть ошибки с любым типом узлов: "матрица коллокаций сингулярна". Я думаю, что обработка граничных условий нацелена на кубические сплайны.