Числовая радиальная производная функции, вычисленной на декартовой сетке - PullRequest
0 голосов
/ 19 июня 2020

У меня есть радиально-симметричная c функция, вычисленная на трехмерной декартовой сетке. Как я могу численно вычислить радиальную производную функции?

Для простого примера (сферический гауссиан) вычислите производные df / dx, df / dy и df / dz:

# Parameters
start = 0
end = 5
n = 20

# Variables
x = np.linspace(start, end, num=n)
y = np.linspace(start, end, num=n)
z = np.linspace(start, end, num=n)
dx = (end - start) / n
dy = (end - start) / n
dz = (end - start) / n
x_grid, y_grid, z_grid = np.meshgrid(x, y, z)
eval_xyz = np.exp(-(x_grid ** 2 + y_grid ** 2 + z_grid ** 2))

# Allocate
df_dx = np.zeros((n, n, n))
df_dy = np.zeros((n, n, n))
df_dz = np.zeros((n, n, n))

# Calculate Cartesian gradient numerically
for x in range(eval_xyz.shape[0] - 1):
    for y in range(eval_xyz.shape[1] - 1):
        for z in range(eval_xyz.shape[2] - 1):

            df_dx[x, y, z] = (eval_xyz[x + 1, y, z] - eval_xyz[x, y, z]) / dx
            df_dy[x, y, z] = (eval_xyz[x, y + 1, z] - eval_xyz[x, y, z]) / dy
            df_dz[x, y, z] = (eval_xyz[x, y, z + 1] - eval_xyz[x, y, z]) / dz

Возможно ли тогда легко вычислить радиальную производную df / dr по декартовым производным?

Ответы [ 2 ]

0 голосов
/ 21 июня 2020

Ваш собственный ответ - шаг в правильном направлении, но есть некоторые проблемы как в ответе, так и в коде, генерирующем декартовы производные.

В этих строках есть проблема:

x = np.linspace(start, end, num=n)
dx = (end - start) / n

Размер шага на самом деле (end-start)/(n-1).

Здесь:

x_grid, y_grid, z_grid = np.meshgrid(x, y, z)
df_dx[x, y, z] = (eval_xyz[x + 1, y, z] - eval_xyz[x, y, z]) / dx

вы попали в ловушку настройки meshgrid по умолчанию: meshgrid(np.arange(n1), np.arange(n2)) вернет массивы в форма (n2, n1), если вы не добавите параметр indexing='ij'. Поскольку у вас размер n во всех измерениях, вы не получите предупреждений об ошибках индексации, но вы, возможно, потратите много времени, пытаясь отладить, почему числа не имеют смысла.

Когда вы манипулируете многомерными массивов, рекомендуется установить размеры в разных направлениях на несколько разные значения, чтобы вы могли легко проверить, соответствуют ли формы массива тем, которые вы хотите.

Кроме того, вы должны обычно оценивать производную как (f[i+1]-f[i-1])/(2*dx), что верно до второго порядка по x.

for x in range(eval_xyz.shape[0] - 1):
    for y in range(eval_xyz.shape[1] - 1):
        for z in range(eval_xyz.shape[2] - 1):

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

Вот код, который вычисляет декартову производную, а затем радиальную производную.

import numpy as np

def get_cartesian_gradient(f, xyzsteps):
    """For f shape (nx, ny, nz), return gradient as (3, nx, ny, nz) shape.
    
    xyzsteps is a (3,) array.
    
    Note: edge points of the gradient array are set to NaN.
    (Exercise for the reader to implement those).
    """
    
    fshape = f.shape
    grad = np.full((3,) + fshape, np.nan, dtype=np.float64)
    
    sl, sm, sr = slice(0, -2), slice(1, -1), slice(2, None)

    # Note: multiplying is faster than dividing.
    grad[0, sm, sm, sm] = (f[sr, sm, sm] - f[sl, sm, sm]) * (0.5/xyzsteps[0])
    grad[1, sm, sm, sm] = (f[sm, sr, sm] - f[sm, sl, sm]) * (0.5/xyzsteps[1])
    grad[2, sm, sm, sm] = (f[sm, sm, sr] - f[sm, sm, sl]) * (0.5/xyzsteps[2])
    
    return grad

def get_dfdr_from_cartesian(grad, x1s, y1s, z1s):
    """Return df/dr array from gradient(f).
    
    grad.shape must be (3, nx, ny, nz)
    return shape (nx, ny, nz).
    """
    _, nx, ny, nz = grad.shape
    
    # we need sin(theta), cos(theta), sin(phi), and cos(phi)
    
    # rxy: shape (nx, ny, 1)
    rxy = np.sqrt(x1s.reshape(-1, 1, 1)**2 + y1s.reshape(1, -1, 1)**2)
    # r: shape (nx, ny, nz)
    r = np.sqrt(rxy**2 + z1s.reshape(1, 1, -1)**2)
    
    # change zeros to NaN
    r = np.where(r==0, np.nan, r)
    rxy = np.where(rxy==0, np.nan, rxy)
    
    cos_theta = z1s.reshape(1, 1, -1) / r
    sin_theta = rxy / r
    cos_phi = x1s.reshape(-1, 1, 1) / rxy
    sin_phi = y1s.reshape(1, -1, 1) / rxy
    
    # and the derivative
    dfdr = (grad[0]*cos_phi + grad[1]*sin_phi)*sin_theta + grad[2]*cos_theta
    return dfdr

x1s = np.linspace(-1, 1, 19)
y1s = np.linspace(-1, 1, 21)
z1s = np.linspace(-1, 1, 23)

xs, ys, zs = np.meshgrid(x1s, y1s, z1s, indexing='ij')
xyzsteps = [x1s[1]-x1s[0], y1s[1]-y1s[0], z1s[1]-z1s[0]]

def func(x, y, z):
    return x**2 + y**2 + z**2
    
def dfdr_analytical(x, y, z):
    r = np.sqrt(x**2 + y**2 + z**2)
    return 2*r
    

# grad has shape (3, nx, ny, nz)
grad = get_cartesian_gradient(func(xs, ys, zs), xyzsteps)

dfdr = get_dfdr_from_cartesian(grad, x1s, y1s, z1s)

# test
diff = dfdr - dfdr_analytical(xs, ys, zs)
assert np.nanmax(np.abs(diff)) < 1e-14

Обратите внимание, что я решил возвращать значения NaN для точек на z- оси, потому что df / dr там не определено, если только f (x, y, z) не является вращательно симметричным c вокруг оси z и имеет df / dr = 0 во всех направлениях. Это то, что не гарантируется для произвольного набора данных.

Причина замены нулей в знаменателях на np.nan с использованием np.where заключается в том, что при делении на ноль будут появляться предупреждающие сообщения, а при делении на nan выиграет. т.

0 голосов
/ 20 июня 2020

Уловка состоит в том, чтобы express радиальные производные представлялись суммой декартовых производных с учетом тета и фи в каждой точке, которые могут быть выражены в декартовых координатах как: Уравнение

Таким образом, код становится:

theta_val = theta(i * dx, j * dy, k * dz)
phi_val = phi(i * dx, j * dy)
            
df_dr[i, j, k] = df_dx[i, j, k] * np.sin(theta_val) * np.cos(phi_val) \
                 + df_dy[i, j, k] * np.sin(theta_val) * np.sin(phi_val) \
                 + df_dz[i, j, k] * np.cos(theta_val)

Где тета и фи вычисляются тщательно, чтобы иметь дело с делением на ноль

def theta(x, y, z):

    if x == 0 and y == 0 and z == 0:
        return 0

    elif z == 0:
        return np.pi / 2

    elif x == 0 and y == 0:
        return 0

    else:
        return np.arctan(np.sqrt(x ** 2 + y ** 2) / z)


def phi(x, y):

    if x == 0 and y == 0:
        return 0

    elif x == 0:
        return np.pi / 2

    elif y == 0:
        return 0

    else:
        return math.atan2(y, x)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...