Ciao Lucagalbu,
premetto di non aver mai utilizzato le librerie numpy col C, in ogni caso dando uno sguardo alla documentazione ufficiale (Python Types and C-Structures), la struttura dati di PyArrayObject che viene fuori è la seguente:
typedef struct PyArrayObject {
PyObject_HEAD
char *data;
int nd;
npy_intp *dimensions;
npy_intp *strides;
PyObject *base;
PyArray_Descr *descr;
int flags;
PyObject *weakreflist;
} PyArrayObject;
Ciò che vai ad usare nel tuo codice sono i valori memorizzati in data e strides, che rispettivamente individuano il puntatore al primo elemento dell'array, e un array di interi che contiene il numero dei byte da saltare per raggiungere il successivo elemento dell'array per ogni dimensione.
Le tue due scritture non sono affatto equivalenti.
Nel 1° caso:
double *ptr2 = (double *) (ptr->data + k*ptr->stride[0]);
Qui, secondo l'aritmetica dei puntatori, ti stai spostando al k-esimo elemento dell'array (dal momento che data è un puntatore a char e stride indica il numero di byte da saltare per passare all'elemento successivo).
Nel 2° caso:
double *ptr2 = (double *) ptr->data;
Qui sei fermo al primo elemento puntato da data.
Il primo ed il secondo caso possono coincidere solo se k (vedi 1° caso) è uguale a zero.