并行计算奇异值分解--Jacobi旋转
鉴于矩阵的奇异值分解SVD在工程领域的广泛应用(如数据压缩、噪声去除、数值分析等等,包括在NLP领域的潜在语义索引LSI核心操作也是SVD),今天就详细介绍一种SVD的实现方法--Jacobi旋转法。跟其它SVD算法相比,Jacobi法精度高,虽然速度慢,但容易并行实现。
一些链接
http://cdmd.cnki.com.cn/Article/CDMD-10285-1012286387.htm 并行JACOBI方法求解矩阵奇异值的研究。本文呈现的代码就是依据这篇论文写出来的。
http://math.nist.gov/javanumerics/jama/ Jama包是用于基本线性代数运算的java包,提供矩阵的cholesky分解、LUD分解、QR分解、奇异值分解,以及PCA中要用到的特征值分解,此外可以计算矩阵的乘除法、矩阵的范数和条件数、解线性方程组等。
http://users.telenet.be/paul.larmuseau/SVD.htm 在线SVD运算器。
http://www.bluebit.gr/matrix-calculator/ bluebit在线矩阵运算器,提供矩阵的各种运算。
http://www.drque.net/Projects/Matrix/ C++ Matrix library提供矩阵的加减乘除、求行列式、LU分解、求逆、求转置。本文的头两段程序就引用了这里面的matrix.h。
基于双边Jacobi旋转的奇异值分解算法
V是A的右奇异向量,也是的特征向量;
U是A的左奇异向量,也是的特征向量。
特别地,当A是对称矩阵的时候,=,即U=V,U的列向量不仅是的特征向量,也是A的特征向量。这一点在主成分分析中会用到。
对于正定的对称矩阵,奇异值等于特征值,奇异向量等于特征向量。特征值分解和奇异值分解的关系知乎上解释得很好。
U、V都是正交矩阵,满足矩阵的转置即为矩阵的逆。
双边Jacobi方法本来是用来求解对称矩阵的特征值和特征向量的,由于就是对称矩阵,求出的特征向量就求出了A的右奇异值,的特征值开方后就是A的奇异值。
一个Jacobi旋转矩阵J形如:
它就是在一个单位矩阵的基础上改变p行q行p列q列四个交点上的值,J矩阵是一个标准正交矩阵。
当我们要对H和p列和q列进行正交变换时,就对H施行:
在一次迭代过程当中需要对A的任意两列进行一次正交变换,迭代多次等效于施行
迭代的终止条件是为对角矩阵,即原矩阵H进过多次的双边Jacobi旋转后非对角线元素全部变成了0(实现计算当中不可能真的变为0,只要小于一个很小的数就可以认为是0了)。
每一个J都是标准正交阵,所以也是标准正交阵,记为V,则是,则。从此式也可以看出对称矩阵的左奇异向量和右奇异向量是相等的。V也是H的特征向量。
当特征值是0时,对应的Ui和Vi不用求,我们只需要U和V的前r列就可以恢复矩阵A了(r是非0奇异值的个数),这也正是奇异值分解可以进行数据压缩的原因。
#include"matrix.h" #include<cmath> #include<map> using namespace std; const double THRESHOLD = 1E-8; const int ITERATION = 30; //迭代次数的上限 inline int sign(double number) { if (number < 0) return -1; else return 1; } void rotate(Matrix < double >&matrix, int i, int j, bool & pass, Matrix < double >&J) { double ele = matrix.get(i, j); if (fabs(ele) < THRESHOLD) { return; } pass = false; double ele1 = matrix.get(i, i); double ele2 = matrix.get(j, j); int size=matrix.getRows(); double tao = (ele1 - ele2) / (2 * ele); double tan = sign(tao) / (fabs(tao) + sqrt(1 + pow(tao, 2))); double cos = 1 / sqrt(1 + pow(tan, 2)); double sin = cos * tan; Matrix < double >G(IdentityMatrix < double >(size, size)); G.put(i, i, cos); G.put(i, j, -1 * sin); G.put(j, i, sin); G.put(j, j, cos); matrix = G.getTranspose() * matrix * G; J *= G; } void jacobi(Matrix < double >&matrix, int size, vector < double >&E, Matrix < double >&J) { int iteration = ITERATION; while (iteration-- > 0) { bool pass = true; for (int i = 0; i < size; ++i) { for (int j = i+1; j < size; ++j) { rotate(matrix, i, j, pass, J); } } if (pass) //当非对角元素全部变为0时迭代退出 break; } cout << "迭代次数:" << ITERATION - iteration << endl; for (int i = 0; i < size; ++i) { E[i] = matrix.get(i, i); if (E[i] < THRESHOLD) E[i] = 0.0; } } void svd(Matrix < double >&A, Matrix < double >&U, Matrix < double >&V, vector < double >&E) { int rows = A.getRows(); int columns = A.getColumns(); assert(rows <= columns); assert(U.getRows() == rows); assert(U.getColumns() == rows); assert(V.getRows() == columns); assert(V.getColumns() == columns); assert(E.size() == columns); Matrix < double >B = A.getTranspose() * A; //A的转置乘以A,得到一个对称矩阵B Matrix < double >J(IdentityMatrix < double >(columns, columns)); vector < double >S(columns); jacobi(B, columns, S, J); //求B的特征值和特征向量 for (int i = 0; i < S.size(); ++i) S[i] = sqrt(S[i]); //B的特征值开方后得到A的奇异值 /*奇异值按递减排序,对应的V中的特征向量也要重排序 */ multimap < double, int >eigen; for (int i = 0; i < S.size(); ++i) //在multimap内部自动按key进行排序 eigen.insert(make_pair(S[i], i)); multimap < double, int >::const_iterator iter = --eigen.end(); int num_eig = 0; //记录非0奇异值的个数 for (int i = 0; i < columns; ++i, iter--) { //反向遍历multimap,使奇异值从大到小排序 int index = iter->second; E[i] = S[index]; if (E[i] > THRESHOLD) { num_eig++; } for (int row = 0; row < columns; ++row) V.put(row, i, J.get(row, index)); } assert(num_eig <= rows); for (int i = 0; i < num_eig; ++i) { Matrix < double >vi = V.getColumn(i); //获取V的第i列 double sigma = E[i]; Matrix < double >ui(rows, 1); ui = A * vi; for (int j = 0; j < rows; ++j) { U.put(j, i, ui.get(j, 0) / sigma); } } //U矩阵的后(rows-none_zero)列就不计算了,采用默认值0。因为这后几列对应的奇异值为0,在做数据压缩时用不到这几列 } int main(int argc, char *argv[]) { const int ROW = 2; const int COL = 3; assert(ROW <= COL); double arr[ROW * COL] = {1,1,0,0,0,1}; Matrix < double >A(ROW, COL); A = arr; Matrix < double >U(ROW, ROW); Matrix < double >V(COL, COL); vector < double >E(COL); svd(A, U, V, E); Matrix < double >Sigma(ROW, COL); for (int i = 0; i < ROW; ++i) Sigma.put(i, i, E[i]); cout << "U=" << endl << U; cout << "SIGMA=" << endl << Sigma; cout << "V^T=" << endl << V.getTranspose(); Matrix < double >A_A = U * Sigma * V.getTranspose(); cout << "reset A=" << endl << A_A; return 0; }
基于单边Jacobi旋转的SVD算法
相对于双边,单边的计算量小,并且容易并行实现。
单边Jacobi方法直接对原矩阵A进行单边正交旋转,A可以是任意矩阵。
同样每一轮的迭代都要使A的任意两列都正交一次,迭代退出的条件是B的任意两列都正交。
单边Jacobi旋转有这么一个性质:旋转前若,则旋转后依然是;反之亦然。
把矩阵B每列的模长提取出来,,把记为V,则。
#include"matrix.h" #include<cmath> using namespace std; const double THRESHOLD = 1E-8; const int ITERATION = 30; //迭代次数的上限 inline int sign(double number) { if (number < 0) return -1; else return 1; } void orthogonal (Matrix < double >&matrix, int i, int j, bool & pass,Matrix < double >&V) { assert(i<j); Matrix<double> Ci=matrix.getColumn(i); Matrix<double> Cj=matrix.getColumn(j); double ele = ((Ci.getTranspose())*Cj).get(0,0); if(fabs(ele)<THRESHOLD) //i,j两列已经正交 return; int rows=matrix.getRows(); int columns=matrix.getColumns(); pass = false; double ele1 = ((Ci.getTranspose())*Ci).get(0,0); double ele2 = ((Cj.getTranspose())*Cj).get(0,0); /*只要每次旋转前都把范数大的列放在前面,就可以保证求出的奇异值是递减排序的*/ if(ele1<ele2){ //如果matrix第i列的范数小于第j列,则交换两列.同时V矩阵也作相应的调换 for(int row=0;row<rows;++row){ matrix.put(row,i,Cj.get(row,0)); matrix.put(row,j,Ci.get(row,0)); } for(int row=0;row<columns;++row){ double tmp=V.get(row,i); V.put(row,i,V.get(row,j)); V.put(row,j,tmp); } } double tao = (ele1 - ele2) / (2 * ele); double tan = sign(tao) / (fabs(tao) + sqrt(1 + pow(tao, 2))); double cos = 1 / sqrt(1 + pow(tan, 2)); double sin = cos * tan; for(int row=0;row<rows;++row){ double var1=matrix.get(row,i)*cos+matrix.get(row,j)*sin; double var2=matrix.get(row,j)*cos-matrix.get(row,i)*sin; matrix.put(row,i,var1); matrix.put(row,j,var2); } for(int col=0;col<columns;++col){ double var1=V.get(col,i)*cos+V.get(col,j)*sin; double var2=V.get(col,j)*cos-V.get(col,i)*sin; V.put(col,i,var1); V.put(col,j,var2); } } void hestens_jacobi(Matrix < double >&matrix, Matrix < double >&V) { int rows=matrix.getRows(); int columns=matrix.getColumns(); int iteration = ITERATION; while (iteration-- > 0) { bool pass = true; for (int i = 0; i < columns; ++i) { for (int j = i+1; j < columns; ++j) { orthogonal(matrix, i, j, pass, V); //经过多次的迭代正交后,V就求出来了 } } if (pass) //当任意两列都正交时退出迭代 break; } cout << "迭代次数:" << ITERATION - iteration << endl; } int svn(Matrix < double >&matrix, Matrix < double >&S, Matrix < double >&U, Matrix < double >&V) { int rows=matrix.getRows(); int columns=matrix.getColumns(); assert(rows<=columns); hestens_jacobi(matrix,V); vector<double> E(columns); //E中存放奇异值 int none_zero=0; //记录非0奇异值的个数 for (int i = 0; i < columns; ++i) { double norm=sqrt((matrix.getColumn(i).getTranspose()*(matrix.getColumn(i))).get(0,0)); if(norm>THRESHOLD){ none_zero++; } E[i]=norm; } /** * U矩阵的后(rows-none_zero)列以及V的后(columns-none_zero)列就不计算了,采用默认值0。 * 对于奇异值分解A=U*Sigma*V^T,我们只需要U的前r列,V^T的前r行(即V的前r列),就可以恢复A了。r是A的秩 */ for(int row=0;row<rows;++row){ S.put(row,row,E[row]); for(int col=0;col<none_zero;++col){ U.put(row,col,matrix.get(row,col)/E[col]); } } return none_zero; //非奇异值的个数亦即矩阵的秩 } int main(int argc, char *argv[]) { const int ROW = 3; const int COL = 6; assert(ROW <= COL); double arr[ROW * COL] = {6,5,1,9,8,4,8,5,2,4,6,9,1,2,3,2,1,4}; Matrix < double >A(ROW, COL); A = arr; Matrix < double >U(ROW, ROW); IdentityMatrix < double >V(COL, COL); //V初始时是一个单位矩阵 Matrix < double >S(ROW, COL); int rank=svn(A,S,U,V); cout << "U=" << endl << U; cout << "SIGMA=" << endl << S; cout << "V^T=" << endl << (V.getTranspose()); Matrix < double >A_A = U * S * (V.getTranspose()); cout << "reset A=" << endl << A_A; return 0; }
基于单边Jacobi旋转的SVD并行算法
单边Jacobi之于双边Jacobi的一个不同就是每次旋转只影响到矩阵A的两列,因经很多列对的正交旋转变换是可以并行执行的。
基本思想是将A按列分块,每一块分配到一个计算节点上,块内任意两列之间进行单边Jacobi正交变换,所有的计算节点可以同时进行。问题就是矩阵块内部列与列之间进行正交变换是不够的,我们需要所有矩阵块上的任意两列之间都进行正交变换,这就需要计算节点之间交换矩阵的列。本文采用奇偶序列的方法,具体可参考文章 http://cdmd.cnki.com.cn/Article/CDMD-10285-1012286387.htm。
由于这次我用的是C版的MPI(MPI也有C++和Fortan版的),所以上面代码用到的C++版的matrix.h就不能用了,需要自己写一个C版的matrix.h。
matrix.h
#ifndef _MATRIX_H #define _MATRIX_H #include<assert.h> #include<stdlib.h> #include<stdio.h> //初始化一个二维矩阵 double** getMatrix(int rows,int columns){ double **rect=(double**)calloc(rows,sizeof(double*)); int i; for(i=0;i<rows;++i) rect[i]=(double*)calloc(columns,sizeof(double)); return rect; } //返回一个单位矩阵 double** getIndentityMatrix(int rows){ double** IM=getMatrix(rows,rows); int i; for(i=0;i<rows;++i) IM[i][i]=1.0; return IM; } //返回一个矩阵的副本 double** copyMatrix(double** matrix,int rows,int columns){ double** rect=getMatrix(rows,columns); int i,j; for(i=0;i<rows;++i) for(j=0;j<columns;++j) rect[i][j]=matrix[i][j]; return rect; } //从一个一维矩阵得到一个二维矩阵 void getFromArray(double** matrix,int rows,int columns,double *arr){ int i,j,k=0; for(i=0;i<rows;++i){ for(j=0;j<columns;++j){ matrix[i][j]=arr[k++]; } } } //打印二维矩阵 void printMatrix(double** matrix,int rows,int columns){ int i,j; for(i=0;i<rows;++i){ for(j=0;j<columns;++j){ printf("%-10f\t",matrix[i][j]); } printf("\n"); } } //释放二维矩阵 void freeMatrix(double** matrix,int rows){ int i; for(i=0;i<rows;++i) free(matrix[i]); free(matrix); } //获取二维矩阵的某一行 double* getRow(double **matrix,int rows,int columns,int index){ assert(index<rows); double *rect=(double*)calloc(columns,sizeof(double)); int i; for(i=0;i<columns;++i) rect[i]=matrix[index][i]; return rect; } //获取二维矩阵的某一列 double* getColumn(double **matrix,int rows,int columns,int index){ assert(index<columns); double *rect=(double*)calloc(rows,sizeof(double)); int i; for(i=0;i<rows;++i) rect[i]=matrix[i][index]; return rect; } //设置二维矩阵的某一列 void setColumn(double **matrix,int rows,int columns,int index,double *arr){ assert(index<columns); int i; for(i=0;i<rows;++i) matrix[i][index]=arr[i]; } //交换矩阵的某两列 void exchangeColumn(double **matrix,int rows,int columns,int i,int j){ assert(i<columns); assert(j<columns); int row; for(row=0;row<rows;++row){ double tmp=matrix[row][i]; matrix[row][i]=matrix[row][j]; matrix[row][j]=tmp; } } //得到矩阵的转置 double** getTranspose(double **matrix,int rows,int columns){ double **rect=getMatrix(columns,rows); int i,j; for(i=0;i<columns;++i){ for(j=0;j<rows;++j){ rect[i][j]=matrix[j][i]; } } return rect; } //计算两向量内积 double vectorProduct(double *vector1,double *vector2,int len){ double rect=0.0; int i; for(i=0;i<len;++i) rect+=vector1[i]*vector2[i]; return rect; } //两个矩阵相乘 double** matrixProduct(double **matrix1,int rows1,int columns1,double **matrix2,int columns2){ double **rect=getMatrix(rows1,columns2); int i,j; for(i=0;i<rows1;++i){ for(j=0;j<columns2;++j){ double *vec1=getRow(matrix1,rows1,columns1,i); double *vec2=getColumn(matrix2,columns1,columns2,j); rect[i][j]=vectorProduct(vec1,vec2,columns1); free(vec1); free(vec2); } } return rect; } //得到某一列元素的平方和 double getColumnNorm(double** matrix,int rows,int columns,int index){ assert(index<columns); double* vector=getColumn(matrix,rows,columns,index); double norm=vectorProduct(vector,vector,rows); free(vector); return norm; } //打印向量 void printVector(double* vector,int len){ int i; for(i=0;i<len;++i) printf("%-15.8f\t",vector[i]); printf("\n"); } #endif
svd.c
#include"mpi.h" #include"matrix.h" #include<string.h> #include<stdlib.h> #include<math.h> //gcc编译的时候需要加-lm选项 #define THREASHOLD 1e-8 #define ITERATION 20 #define ROW 3 //每个计算节点上的矩阵块有3行4列 #define COL 3 #define LINELEN 5*COL //矩阵文件每一行的长度 int sign(double number) { if(number<0) return -1; else return 1; } int myRank; //计算结点的序号 int procSize; //计算结点的数目 MPI_Status status; //存储状态变量 /*从文件中读取矩阵*/ void readFromFile(double **matrix,int row,int col,char* file){ FILE *fp; int len=col*10; char *buf=(char*)calloc(len,sizeof(char)); if((fp=fopen(file,"r"))==NULL){ perror("fopen"); printf("%s\n",file); exit(1); } int i,j; for(i=0;i<row;++i){ if(fgets(buf,len,fp)==NULL){ fprintf(stderr,"文件的行数小于矩阵需要的行数\n"); exit(1); } char *seg=strtok(buf,"\t"); double ele=atof(seg); matrix[i][0]=ele; for(j=1;j<col;++j){ if((seg=strtok(NULL,"\t"))==NULL){ fprintf(stderr,"文件的列数小于矩阵需要的列数\n"); exit(1); } ele=atof(seg); matrix[i][j]=ele; } memset(buf,0x00,len); } free(buf); fclose(fp); } /*把矩阵写入文件*/ void writeToFile(double **matrix,int rows,int columns,char* file){ FILE *fp; if((fp=fopen(file,"w"))==NULL){ perror("fopen"); exit(1); } fprintf(fp,"%d\t%d\n",rows,columns); //文件首行记录矩阵的行数和列数 int i,j; for(i=0;i<rows;++i){ for(j=0;j<columns;++j){ fprintf(fp,"%-10f\t",matrix[i][j]); } fprintf(fp,"\n"); } fclose(fp); } /*把向量写入文件*/ void vectorToFile(double *vector,int len,char* file){ FILE *fp; if((fp=fopen(file,"w"))==NULL){ perror("fopen"); exit(1); } int i; for(i=0;i<len;++i){ fprintf(fp,"%-10f\t",vector[i]); } fclose(fp); } /*两个向量进行单边Jacobi正交变换*/ void orthogonalVector(double *Ci,double *Cj,int len1,double *Vi,double *Vj,int len2,int *pass){ double ele=vectorProduct(Ci,Cj,len1); if(fabs(ele)<THREASHOLD) return; //如果两列已经正交,不需要进行变换,则返回true *pass=0; double ele1=vectorProduct(Ci,Ci,len1); double ele2=vectorProduct(Cj,Cj,len1); double tao=(ele1-ele2)/(2*ele); double tan=sign(tao)/(fabs(tao)+sqrt(1+pow(tao,2))); double cos=1/sqrt(1+pow(tan,2)); double sin=cos*tan; int row; for(row=0;row<len1;++row){ double var1=Ci[row]*cos+Cj[row]*sin; double var2=Cj[row]*cos-Ci[row]*sin; Ci[row]=var1; Cj[row]=var2; } for(row=0;row<len2;++row){ double var1=Vi[row]*cos+Vj[row]*sin; double var2=Vj[row]*cos-Vi[row]*sin; Vi[row]=var1; Vj[row]=var2; } } /*矩阵的两列进行单边Jacobi正交变换。V是方阵,行/列数为columns*/ void orthogonal(double **matrix,int rows,int columns,int i,int j,int *pass,double **V){ assert(i<j); double* Ci=getColumn(matrix,rows,columns,i); double* Cj=getColumn(matrix,rows,columns,j); double* Vi=getColumn(V,columns,columns,i); double* Vj=getColumn(V,columns,columns,j); orthogonalVector(Ci,Cj,rows,Vi,Vj,columns,pass); int row; for(row=0;row<rows;++row){ matrix[row][i]=Ci[row]; matrix[row][j]=Cj[row]; } for(row=0;row<columns;++row){ V[row][i]=Vi[row]; V[row][j]=Vj[row]; } free(Ci); free(Cj); free(Vi); free(Vj); } void normalize(double **A,int rows,int columns){ double *sigular=(double*)calloc(columns,sizeof(double)); int i,j; for(i=0;i<columns;++i){ double *vector=getColumn(A,rows,columns,i); double norm=sqrt(vectorProduct(vector,vector,rows)); sigular[i]=norm; } char outFileS[7]={'S','X','.','m','a','t','\0'}; outFileS[1]='0'+myRank; vectorToFile(sigular,columns,outFileS); double **U=getMatrix(rows,columns); for(j=0;j<columns;++j){ if(sigular[j]==0) for(i=0;i<rows;++i) U[i][j]=0; else for(i=0;i<rows;++i) U[i][j]=A[i][j]/sigular[j]; } char outFileU[7]={'U','X','.','m','a','t','\0'}; outFileU[1]='0'+myRank; writeToFile(U,rows,columns,outFileU); free(sigular); freeMatrix(U,rows); } void main(int argc, char *argv[]) { MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myRank); MPI_Comm_size(MPI_COMM_WORLD,&procSize); assert(myRank<10); int totalColumn=COL*procSize; //算出原矩阵总共有多少列 /*准备矩阵块A和V*/ char matrixFile[11]={'b','l','o','c','k','X','.','m','a','t','\0'}; matrixFile[5]='0'+myRank; double **A=getMatrix(ROW,COL); readFromFile(A,ROW,COL,matrixFile); double **V=getMatrix(totalColumn,COL); int j; for(j=0;j<COL;++j){ V[COL*myRank+j][j]=1.0; } /*开始进行单边Jacobi旋转迭代*/ int iteration=ITERATION; while(iteration-->0){ /*奇数次迭代后矩阵按列范数从大到小排列;偶数次迭代后矩阵按列范数从小到大排列*/ int pass=1; int allpass=0; /*每个计算节点上相邻两列进行单边Jacobi变换*/ int i; for(i=1;i<=totalColumn;++i){ //原矩阵有几列就需要做几轮的交换 int j; int send=0,recv=0; //send记录是否要把本矩阵块的最后一列发送给下一个计算结点;recv记录是否要从上一个计算结点接收一列数据 int mod1=i%2; //余数为0时是奇序,否则为偶序 int mod2=(myRank*COL)%2; //判断本块的第1列(首列)是否为原矩阵的第奇数列,为0则是,为1则不是 if(mod1^mod2){ //融合了奇序和偶序的情况 j=0; //首列可以和第二列进行正交变换 } else{ j=1; //首列需要和上一个计算结点的最后一列进行正交变换 if(myRank>0){ //不是第1个计算节点 recv=1; //需要从上一个计算节点接收最后一列 } } for(++j;j<COL;j+=2){ //第j列与j-1列进行单边Jacobi正交变换 orthogonal(A,ROW,COL,j-1,j,&pass,V,totalColumn); exchangeColumn(A,ROW,COL,j-1,j); //正交变换之后交换两列 exchangeColumn(V,totalColumn,COL,j-1,j); } if(j==COL){ //最后一列剩下了,无法配对,它需要发送给下一个计算节点 if(myRank<procSize-1){ //如果不是最后1个计算节点 send=1; //需要把最后一列发给下一个计算节点 } } if(send){ //把最后一列发给下一个计算节点 double *lastColumnA=getColumn(A,ROW,COL,COL-1); double *lastColumnV=getColumn(V,totalColumn,COL,COL-1); MPI_Send(lastColumnA,ROW,MPI_DOUBLE,myRank+1,59,MPI_COMM_WORLD); MPI_Send(lastColumnV,totalColumn,MPI_DOUBLE,myRank+1,60,MPI_COMM_WORLD); free(lastColumnA); free(lastColumnV); } if(recv){ //从上一个计算节点接收最后一列 double* preColumnA=(double*)calloc(ROW,sizeof(double)); double* preColumnV=(double*)calloc(totalColumn,sizeof(double)); MPI_Recv(preColumnA,ROW,MPI_DOUBLE,myRank-1,59,MPI_COMM_WORLD,&status); MPI_Recv(preColumnV,totalColumn,MPI_DOUBLE,myRank-1,60,MPI_COMM_WORLD,&status); //本行首列与上一个计算节点末列进行正交变换 double* firstColumnA=getColumn(A,ROW,COL,0); double* firstColumnV=getColumn(V,totalColumn,COL,0); orthogonalVector(preColumnA,firstColumnA,ROW,preColumnV,firstColumnV,totalColumn,&pass); //把preColumn留下 setColumn(A,ROW,COL,0,preColumnA); setColumn(V,totalColumn,COL,0,preColumnV); //把firstColumn发送给上一个计算结点 MPI_Send(firstColumnA,ROW,MPI_DOUBLE,myRank-1,49,MPI_COMM_WORLD); MPI_Send(firstColumnV,totalColumn,MPI_DOUBLE,myRank-1,50,MPI_COMM_WORLD); free(preColumnA); free(preColumnV); free(firstColumnA); free(firstColumnV); } if(send){ //把最后一列发给下一个计算节点后,下一个计算节点做完了正交变换,又把一列发送回来了,现在需要接收 double* nextColumnA=(double*)calloc(ROW,sizeof(double)); double* nextColumnV=(double*)calloc(totalColumn,sizeof(double)); MPI_Recv(nextColumnA,ROW,MPI_DOUBLE,myRank+1,49,MPI_COMM_WORLD,&status); MPI_Recv(nextColumnV,totalColumn,MPI_DOUBLE,myRank+1,50,MPI_COMM_WORLD,&status); //把接收到的那一列赋给本块的最后一列 setColumn(A,ROW,COL,COL-1,nextColumnA); setColumn(V,totalColumn,COL,COL-1,nextColumnV); free(nextColumnA); free(nextColumnV); } } MPI_Barrier(MPI_COMM_WORLD); //各个计算节点都完成一次迭代后,汇总一上是不是所有的都pass了 MPI_Reduce(&pass,&allpass,1,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD); MPI_Bcast(&allpass,1,MPI_INT,0,MPI_COMM_WORLD); //把是否allpass告知所有节点 if(allpass==procSize) break; //退出迭代 } if(myRank==0){ printf("迭代次数:%d\n",ITERATION-iteration-1); } char outFileV[7]={'V','X','.','m','a','t','\0'}; outFileV[1]='0'+myRank; writeToFile(V,totalColumn,COL,outFileV); normalize(A,ROW,COL); freeMatrix(A,ROW); freeMatrix(V,totalColumn); MPI_Finalize(); }
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/2575948.html