C 使用摄影矢量访问多维阵列的任意轴向切片?
我正在构建一套与a一起使用的函数,我希望能够定义数组的任意切片,这样我就可以实现两个任意矩阵(也称为张量或n-d数组)的广义内积 我读过的一篇APL论文(我真的找不到哪篇——我读了这么多)定义了矩阵左侧的矩阵积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定义 通用数组切片可以通过一个摄影向量或描述符引用每个数组来实现
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;
}