Норма памяти - Cython - PullRequest
       46

Норма памяти - Cython

0 голосов
/ 12 октября 2018

У меня есть функция, которой присваивается вектор обзора памяти, и я хочу вычислить норму этого вектора.До сих пор я добивался этого путем преобразования обзора памяти в массив Numpy и вычисления нормы с помощью np.sqrt(V.dot(V)).Теперь я хочу избавиться от этого шага по соображениям скорости, но программа в какой-то момент завершается ошибкой со следующей реализацией.

cdef do_something(np.double_t[::1] M_mem):
    cdef:
        int i
        np.double_t norm_mv = 0
        np.double_t norm_np = 0
        np.ndarray[np.double_t, ndim=1] V = np.copy(np.asarray(M_mem))

    # Original implementation -- working
    norm_np = np.sqrt(V.dot(V))

    # My failed try with memoryview -- not working
    for i in range(M_mem.shape[0]):
        norm_mv += M_mem[i]**2
    norm_mv = np.sqrt(norm_mv)

    # norm_mv != norm_np

Я подозреваю, что причиной является Арифметика с плавающей точкой что мешает для достаточно больших векторов.Существует ли численно стабильный способ вычисления нормы просмотра памяти Cython?

ОБНОВЛЕНИЕ

После проверки выясняется, что ошибка округления, вероятно, не имеет смысла.Вместо этого происходит действительно странная вещь.Моя настоящая функция выглядит следующим образом:

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] GS_coefficients(np.double_t[:,::1] M_mem):
    cdef:
        int n, i, k
        int N_E = M_mem.shape[1]
        np.ndarray[np.double_t, ndim=2] W = np.asarray(M_mem)
        np.ndarray[np.double_t, ndim=2] V = np.copy(W)
        np.double_t[:,::1] G = np.eye(N_E, dtype=np.float64)
        np.longdouble_t norm  = 0 # np.sqrt(V[:,0].dot(V[:,0]))
    for i in range(M_mem.shape[0]):
        norm += M_mem[i,0]**2
    norm = sqrt(norm)
    print("npx: ", np.sqrt(V[:,0].dot(V[:,0]))) # line 1
    print("cp: ", norm) # line 2
    V[:,0] /= norm
    G[0,0] /= norm
    for n in range(1, N_E):
        for i in range(0, n):
            G[n,i] = - (V[:,i].dot(W[:,n]))
            V[:,n] += G[n,i] * V[:,i]
        norm = np.sqrt(V[:,n].dot(V[:,n]))
        V[:,n] /= norm
        for i in range(n+1):
            G[n,i] /= norm
    return G

Я вставил операторы print, чтобы проверить, "насколько равны" результаты для norm.Странность в том, что теперь все работает нормально, как показано выше.Но когда я закомментирую первый оператор печати (строка 1), код нормально проходит через функцию, но вскоре завершается ошибкой в ​​программе.То, что там происходит?Разве это не просто оператор print, который не должен влиять на операционную деятельность?

ОБНОВЛЕНИЕ 2

Вот моя попытка минимального, полного и проверяемого примера:

DEF N_E_cpt = 4

cimport cython
cimport numpy as np
import numpy as np
from libc.math cimport sqrt

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] GS_coefficients(np.double_t[:,::1] M_mem):
    """Writes the coefficients, that the Gram-Schmidt procedure
    provides in a Matrix and retruns it."""
    cdef:
        int n, i, k
        int N_E = M_mem.shape[1]
        np.ndarray[np.double_t, ndim=2] W = np.asarray(M_mem)
        np.ndarray[np.double_t, ndim=2] V = np.copy(W)
        np.double_t[:,::1] G = np.eye(N_E, dtype=np.float64)
        np.longdouble_t norm  = 0 # np.sqrt(V[:,0].dot(V[:,0]))
    for i in range(M_mem.shape[0]):
        norm += M_mem[i,0]**2
    norm = sqrt(norm)
    print("npx: ", np.sqrt(V[:,0].dot(V[:,0]))) # line 1
    print("cp: ", norm) # line 2
    V[:,0] /= norm
    G[0,0] /= norm
    for n in range(1, N_E):
        for i in range(0, n):
            G[n,i] = - (V[:,i].dot(W[:,n]))
            V[:,n] += G[n,i] * V[:,i]
        norm = np.sqrt(V[:,n].dot(V[:,n]))
        V[:,n] /= norm
        for i in range(n+1):
            G[n,i] /= norm
    return G

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] G_mat(np.double_t[:,::1] M_mem):
    """Calls GS_coefficients and uses the coefficients to calculate
    the entries of the transformation matrix G_ij"""
    cdef:
        np.double_t[:,::1] G_mem = GS_coefficients(M_mem)
        int N_E = G_mem.shape[1]
        np.double_t carr[N_E_cpt][N_E_cpt]
        np.double_t[:,::1] G = carr
        int n, i, j

    # delete lower triangle in G
    G[...] = G_mem
    for i in range(N_E_cpt):
        for j in range(0, i):
            G[i,j] = 0.

    for n in range(1, N_E):
        for i in range(0, n):
            for j in range(0, i+1):
                G[n,j] += G_mem[n,i] * G[i,j]
    return G


def run_test():
    cdef:
        np.double_t[:,::1] A_mem
        np.double_t[:,::1] G
        np.ndarray[np.double_t, ndim=2] A = np.random.rand(400**2, N)
        int N = 4

    A_mem = A
    G = G_mat(A_mem)
    X = np.zeros((400**2, N))
    for i in range(0, N):
        for j in range(0,i+1):
            X[:,i] += G[i,j] * A[:,j]
    print(X)
    print("\n", X.T.dot(X))

run_test()

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

Так что этот код должен взять неортонормальный набор векторов, записанных как векторы столбцов в Матрице A, и вернутьматрица ортонормирования, которая ортонормирует множество векторов следующим образом:

Formula for orthonormalization in code style

Таким образом, A_ {orthonormal} эквивалентна X-матрице в коде.Когда вы умножаете транспонирование ортонормированной матрицы на саму ортонормированную матрицу, вы получаете единичную матрицу, то есть то, что вы получаете, пока там находится print оператор # line1.Как только вы удалите его, вы также получите недиагональные записи, что означает, что матрица даже не ортогональна.Почему?

1 Ответ

0 голосов
/ 12 октября 2018

Существует как минимум опечатка

for i in range(M_mem.shape[0]):
    norm += M_mem[i]**2

->

for i in range(M_mem.shape[0]):
    norm_mv += M_mem[i]**2

Иначе, я рекомендую более идиоматическую версию ниже:

import numpy as np
cimport numpy as np
from libc.math cimport sqrt

def do_something(double[::1] M_mem):
    cdef:
        int i
        double norm_mv = 0
        double norm_np = 0
        double[::1] V = np.copy(np.asarray(M_mem))

    # Original implementation -- working
    norm_np = np.sqrt(np.dot(V, V))

    # My failed try with memoryview -- not working
    for i in range(M_mem.shape[0]):
        norm_mv += M_mem[i]**2
    norm_mv = sqrt(norm_mv)

    # norm_mv != norm_np
    return norm_np, norm_mv

import и импортируют numpy и используют скалярные математические функции из libc.math вместо версий NumPy.Вы все еще можете немного ускорить код, украсив подпрограмму @cython.boundscheck(False) (тогда вам нужно cimport cython).

...