C 使用摄影矢量访问多维阵列的任意轴向切片?

C 使用摄影矢量访问多维阵列的任意轴向切片?,c,multidimensional-array,slice,matrix-multiplication,transpose,C,Multidimensional Array,Slice,Matrix Multiplication,Transpose,我正在构建一套与a一起使用的函数,我希望能够定义数组的任意切片,这样我就可以实现两个任意矩阵(也称为张量或n-d数组)的广义内积 我读过的一篇APL论文(我真的找不到哪篇——我读了这么多)定义了矩阵左侧的矩阵积X,维度A;BCDEF和右矩阵Y以及维度G;H我JK其中F==Gas Z <- X +.× Y Z[A;B;C;D;E;H;I;J;K] <- +/ X[A;B;C;D;E;*] × Y[*;H;I;J;K] Z定义 通用数组切片可以通过一个摄影向量或描述符引用每个数组来实现

我正在构建一套与a一起使用的函数,我希望能够定义数组的任意切片,这样我就可以实现两个任意矩阵(也称为张量或n-d数组)的广义内积

我读过的一篇APL论文(我真的找不到哪篇——我读了这么多)定义了矩阵左侧的矩阵积
X
,维度
A;BCDEF
和右矩阵
Y
以及维度
G;H我JK
其中
F==G
as

Z <- X +.× Y
Z[A;B;C;D;E;H;I;J;K] <- +/ X[A;B;C;D;E;*] × Y[*;H;I;J;K]
Z定义
通用数组切片可以通过一个摄影向量或描述符引用每个数组来实现(无论是否内置到语言中),该记录包含第一个数组元素的地址,然后是每个索引的范围和索引公式中的相应系数。这种技术还允许立即进行数组换位、索引反转、子采样等。对于C语言,索引总是从零开始,具有d索引的数组的dope向量至少有1+2d参数。

这是一个密集的段落,但实际上都在里面。所以我们需要这样的数据结构:

struct {
    TYPE *data;  //address of first array element
    int rank; //number of dimensions
    int *dims; //size of each dimension
    int *weight; //corresponding coefficient in the indexing formula
};
其中
TYPE
是元素类型的任意一种,即矩阵的字段。为了简单和具体,我们只使用
int
。出于我自己的目的,我设计了一种不同类型的编码,以便
int
为我完成这项工作,YMMV

所有指针成员都可以在 与结构本身相同的已分配内存块(我们将 调用标题)。但是通过替代早期使用的偏移量 和struct hackery,可以实现算法 独立于内存内部(或外部)的实际内存布局 街区

自包含数组对象的基本内存布局为

rank dims weight data 
     dims[0] dims[1] ... dims[rank-1] 
     weight[0] weight[1] ... weight[rank-1] 
     data[0] data[1] ... data[ product(dims)-1 ] 
间接数组共享数据(整个数组或1个或多个行切片) 将具有以下内存布局

rank dims weight data 
     dims[0] dims[1] ... dims[rank-1] 
     weight[0] weight[1] ... weight[rank-1] 
     //no data! it's somewhere else! 
以及一个间接数组,该数组包含沿 另一个轴将具有与基本间接阵列相同的布局, 但适当修改了dims和重量

具有索引(i0 i1…iN)的元素的访问公式 是

,假设每个索引i[j]在[0…dims[j]之间

在正常布局的行主数组的
权重
向量中,每个元素都是所有较低维度的乘积

for (int i=0; i<rank; i++)
    weight[i] = product(dims[i+1 .. rank-1]);
或者对于7×6×5×4×3×2数组,元数据将是

{ .rank=3, .dims=(int[]){3,4,5}, .weight=(int[]){4*5, 5, 1} }
{ .rank=6, .dims={7,6,5,4,3,2}, .weight={720, 120, 24, 6, 2, 1} }
建设 所以,要创建其中一个,我们需要来自的同一个helper函数来计算维度列表中的大小

/* multiply together rank integers in dims array */
int productdims(int rank, int *dims){
    int i,z=1;
    for(i=0; i<rank; i++)
        z *= dims[i];
    return z;
}
使用与上一个答案相同的技巧,我们可以创建一个变量参数接口,以使使用更简单

/* load rank integers from va_list into int[] dims */
void loaddimsv(int rank, int dims[], va_list ap){
    int i;
    for (i=0; i<rank; i++){
        dims[i]=va_arg(ap,int);
    }
}

/* create a new array with specified rank and dimensions */
arr (array)(int rank, ...){
    va_list ap;
    //int *dims=calloc(rank,sizeof(int));
    int dims[rank];
    int i;
    int x;
    arr z;

    va_start(ap,rank);
    loaddimsv(rank,dims,ap);
    va_end(ap);

    z = arraya(rank,dims);
    //free(dims);
    return z;
}
现在构建其中一个是非常容易的

arr a = array(2,3,4);  // create a dynamic [2][3][4] array
访问元素 通过对
elema
的函数调用检索元素,该函数将每个索引乘以相应的权重,求和,并为
数据
指针编制索引。我们返回指向元素的指针,以便调用者可以读取或修改它

/* access element of a indexed by int[] */
int *elema(arr a, int *ind){
    int idx = 0;
    int i;
    for (i=0; i<a->rank; i++){
        idx += ind[i] * a->weight[i];
    }
    return a->data + idx;
}
收集与参数数组中的-1对应的所有维度和权重,并将其用于创建新的数组标头。所有>=0的参数将乘以其关联的权重,并添加到
数据
指针,将指针偏移到正确的元素

根据数组访问公式,我们将其视为多项式

offset = constant + sum_i=0,n( weight[i] * index[i] )
因此,对于我们从中选择单个元素(+所有较低维度)的任何维度,我们计算出现在的常量索引,并将其添加到公式中的常量项中(在我们的C表示中,它是
数据
指针本身)

辅助函数
casta
使用共享的
数据创建新的数组头
slicea
当然会更改权重值,但是通过计算权重本身,
casta
成为一个更通用的函数。它甚至可以用来构建一个直接在常规数组上运行的动态数组结构C风格的多维数组,因此可以强制转换

转置 摄影向量也可以用来实现转置。维度(和索引)的顺序可以改变

记住,这不是像其他人一样的正常“转置” 是的。我们根本不重新排列数据。这更重要 正确地称为“摄影矢量伪转置”。 我们不需要更改数据,只需更改 访问公式中的常量,重新排列 多项式的系数。在真正意义上,这 这只是交换性和可交换性的一个应用 加法的结合性

因此,为了具体起见,假设数据是经过安排的 按顺序从假设的地址500开始

500: 0 
501: 1 
502: 2 
503: 3 
504: 4 
505: 5 
506: 6 
如果a是秩2,dims{1,7),权重(7,1),那么 指数乘以相关权重的总和 添加到初始指针(500)中,得到适当的 每个元素的地址

a[0][0] == *(500+0*7+0*1) 
a[0][1] == *(500+0*7+1*1) 
a[0][2] == *(500+0*7+2*1) 
a[0][3] == *(500+0*7+3*1) 
a[0][4] == *(500+0*7+4*1) 
a[0][5] == *(500+0*7+5*1) 
a[0][6] == *(500+0*7+6*1) 
因此,dope向量伪转置会重新排列 重量和尺寸与新订购的 指数,但总和保持不变 指针保持不变。数据不移动

b[0][0] == *(500+0*1+0*7) 
b[1][0] == *(500+1*1+0*7) 
b[2][0] == *(500+2*1+0*7) 
b[3][0] == *(500+3*1+0*7) 
b[4][0] == *(500+4*1+0*7) 
b[5][0] == *(500+5*1+0*7) 
b[6][0] == *(500+6*1+0*7) 
内积(又名矩阵乘法) 因此,通过使用通用切片或转置+“行”-切片(更容易),可以实现广义内积

首先,我们需要两个辅助函数,用于将二进制操作应用于两个向量以生成向量结果,并使用二进制操作减少向量以生成标量结果

正如在中一样,我们将传入运算符,因此相同的函数可以用于许多不同的运算符。对于这里的样式,我将运算符作为单个字符进行传递,因此已经存在从C运算符到函数运算符的间接映射。这是一个

表中的额外元素用于空向量的
reduce
函数
/* take a computed slice of a following spec[] instructions
   if spec[i] >= 0 and spec[i] < a->rank, then spec[i] selects
      that index from dimension i.
   if spec[i] == -1, then spec[i] selects the entire dimension i.
 */
arr slicea(arr a, int spec[]){
    int i,j;
    int rank;
    for (i=0,rank=0; i<a->rank; i++)
        rank+=spec[i]==-1;
    int dims[rank];
    int weight[rank];
    for (i=0,j=0; i<rank; i++,j++){
        while (spec[j]!=-1) j++;
        if (j>=a->rank) break;
        dims[i] = a->dims[j];
        weight[i] = a->weight[j];
    }   
    arr z = casta(a->data, rank, dims);
    memcpy(z->weight,weight,rank*sizeof(int));
    for (j=0; j<a->rank; j++){
        if (spec[j]!=-1)
            z->data += spec[j] * a->weight[j];
    }   
    return z;
}
offset = constant + sum_i=0,n( weight[i] * index[i] )
/* create an array header to access existing data in multidimensional layout */
arr casta(int *data, int rank, int dims[]){
    int i,x;
    arr z=malloc(sizeof(struct arr)
            + (rank+rank)*sizeof(int));

    z->rank = rank;
    z->dims = z + 1;
    z->weight = z->dims + rank;
    z->data = data;
    memmove(z->dims,dims,rank*sizeof(int));
    for(x=1, i=rank-1; i>=0; i--){
        z->weight[i] = x;
        x *= z->dims[i];
    }

    return z;
}
500: 0 
501: 1 
502: 2 
503: 3 
504: 4 
505: 5 
506: 6 
a[0][0] == *(500+0*7+0*1) 
a[0][1] == *(500+0*7+1*1) 
a[0][2] == *(500+0*7+2*1) 
a[0][3] == *(500+0*7+3*1) 
a[0][4] == *(500+0*7+4*1) 
a[0][5] == *(500+0*7+5*1) 
a[0][6] == *(500+0*7+6*1) 
b[0][0] == *(500+0*1+0*7) 
b[1][0] == *(500+1*1+0*7) 
b[2][0] == *(500+2*1+0*7) 
b[3][0] == *(500+3*1+0*7) 
b[4][0] == *(500+4*1+0*7) 
b[5][0] == *(500+5*1+0*7) 
b[6][0] == *(500+6*1+0*7) 
#define OPERATORS(_) \
    /* f  F id */ \
    _('+',+,0) \
    _('*',*,1) \
    _('=',==,1) \
    /**/

#define binop(X,F,Y) (binop)(X,*#F,Y)
arr (binop)(arr x, char f, arr y); /* perform binary operation F upon corresponding elements of vectors X and Y */
#define reduce(F,X) (reduce)(*#F,X)
int (reduce)(char f, arr a); /* perform binary operation F upon adjacent elements of vector X, right to left,
                                   reducing vector to a single value */
/* perform binary operation F upon corresponding elements of vectors X and Y */
#define BINOP(f,F,id) case f: *elem(z,i) = *elem(x,i) F *elem(y,i); break;
arr (binop)(arr x, char f, arr y){
    arr z=copy(x);
    int n=x->dims[0];
    int i;
    for (i=0; i<n; i++){
        switch(f){
            OPERATORS(BINOP)
        }
    }
    return z;
}
#undef BINOP
/* perform binary operation F upon adjacent elements of vector X, right to left,
   reducing vector to a single value */
#define REDID(f,F,id) case f: x = id; break;
#define REDOP(f,F,id) case f: x = *elem(a,i) F x; break;
int (reduce)(char f, arr a){
    int n = a->dims[0];
    int x;
    int i;
    switch(f){
        OPERATORS(REDID)
    }
    if (n) {
        x=*elem(a,n-1);
        for (i=n-2;i>=0;i--){
            switch(f){
                OPERATORS(REDOP)
            }
        }
    }
    return x;
}
#undef REDID
#undef REDOP
/* perform a (2D) matrix multiplication upon rows of x and columns of y
   using operations F and G.
       Z = X F.G Y
       Z[i,j] = F/ X[i,*] G Y'[j,*]

   more generally,
   perform an inner product on arguments of compatible dimension.
       Z = X[A;B;C;D;E;F] +.* Y[G;H;I;J;K]  |(F = G)
       Z[A;B;C;D;E;H;I;J;K] = +/ X[A;B;C;D;E;*] * Y[*;H;I;J;K]
 */
arr (matmul)(arr x, char f, char g, arr y){
    int i,j;
    arr xdims = cast(x->dims,1,x->rank);
    arr ydims = cast(y->dims,1,y->rank);
    xdims->dims[0]--;
    ydims->dims[0]--;
    ydims->data++;
    arr z=arraya(x->rank+y->rank-2,catv(xdims,ydims)->data);
    int datasz = productdims(z->rank,z->dims);
    int k=y->dims[0];
    arr xs = NULL;
    arr ys = NULL;

    for (i=0; i<datasz; i++){
        int idx[x->rank+y->rank];
        vector_index(i,z->dims,z->rank,idx);
        int *xdex=idx;
        int *ydex=idx+x->rank-1;
        memmove(ydex+1,ydex,y->rank);
        xdex[x->rank-1] = -1;
        free(xs);
        free(ys);
        xs = slicea(x,xdex);
        ys = slicea(y,ydex);
        z->data[i] = (reduce)(f,(binop)(xs,g,ys));
    }

    free(xs);
    free(ys);
    free(xdims);
    free(ydims);
    return z;
}
/* create an array header to access existing data in multidimensional layout */
arr cast(int *data, int rank, ...){
    va_list ap;
    int dims[rank];

    va_start(ap,rank);
    loaddimsv(rank,dims,ap);
    va_end(ap);

    return casta(data, rank, dims);
}
/* compute vector index list for ravel index ind */
int *vector_index(int ind, int *dims, int n, int *vec){
    int i,t=ind, *z=vec;
    for (i=0; i<n; i++){
        z[n-1-i] = t % dims[n-1-i];
        t /= dims[n-1-i];
    }
    return z;
}