xref: /petsc/src/mat/impls/aij/seq/matlab/aijmatlab.c (revision 8cc058d9cd56c1ccb3be12a47760ddfc446aaffc)
1be1d678aSKris Buschelman 
23b3e256bSKris Buschelman /*
3e3c5b3baSBarry Smith         Provides an interface for the MATLAB engine sparse solver
43b3e256bSKris Buschelman 
53b3e256bSKris Buschelman */
6c6db04a5SJed Brown #include <../src/mat/impls/aij/seq/aij.h>
782c86c8fSBarry Smith #include <petscmatlab.h>
8c6db04a5SJed Brown #include <engine.h>   /* MATLAB include file */
9c6db04a5SJed Brown #include <mex.h>      /* MATLAB include file */
103b3e256bSKris Buschelman 
11aeeaa5c7SBarry Smith #undef __FUNCT__
12aeeaa5c7SBarry Smith #define __FUNCT__ "MatSeqAIJToMatlab"
13*8cc058d9SJed Brown PETSC_EXTERN mxArray *MatSeqAIJToMatlab(Mat B)
14aeeaa5c7SBarry Smith {
15aeeaa5c7SBarry Smith   PetscErrorCode ierr;
16aeeaa5c7SBarry Smith   Mat_SeqAIJ     *aij = (Mat_SeqAIJ*)B->data;
17c088a8dcSBarry Smith   mwIndex        *ii,*jj;
18aeeaa5c7SBarry Smith   mxArray        *mat;
19c088a8dcSBarry Smith   PetscInt       i;
20aeeaa5c7SBarry Smith 
21aeeaa5c7SBarry Smith   PetscFunctionBegin;
22aeeaa5c7SBarry Smith   mat  = mxCreateSparse(B->cmap->n,B->rmap->n,aij->nz,mxREAL);
23aeeaa5c7SBarry Smith   ierr = PetscMemcpy(mxGetPr(mat),aij->a,aij->nz*sizeof(PetscScalar));
24e3c5b3baSBarry Smith   /* MATLAB stores by column, not row so we pass in the transpose of the matrix */
25aeeaa5c7SBarry Smith   jj = mxGetIr(mat);
26aeeaa5c7SBarry Smith   for (i=0; i<aij->nz; i++) jj[i] = aij->j[i];
27aeeaa5c7SBarry Smith   ii = mxGetJc(mat);
28aeeaa5c7SBarry Smith   for (i=0; i<B->rmap->n+1; i++) ii[i] = aij->i[i];
29aeeaa5c7SBarry Smith   PetscFunctionReturn(mat);
30aeeaa5c7SBarry Smith }
31aeeaa5c7SBarry Smith 
32a1d52234SKris Buschelman #undef __FUNCT__
33b3866ffcSBarry Smith #define __FUNCT__ "MatlabEnginePut_SeqAIJ"
34*8cc058d9SJed Brown PETSC_EXTERN PetscErrorCode MatlabEnginePut_SeqAIJ(PetscObject obj,void *mengine)
35a1d52234SKris Buschelman {
36dfbe8321SBarry Smith   PetscErrorCode ierr;
37a1d52234SKris Buschelman   mxArray        *mat;
38a1d52234SKris Buschelman 
39a1d52234SKris Buschelman   PetscFunctionBegin;
40be6adb11SBarry Smith   mat  = MatSeqAIJToMatlab((Mat)obj);if (!mat) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_LIB,"Cannot create MATLAB matrix");
41a1d52234SKris Buschelman   ierr = PetscObjectName(obj);CHKERRQ(ierr);
42a1d52234SKris Buschelman   engPutVariable((Engine*)mengine,obj->name,mat);
43a1d52234SKris Buschelman   PetscFunctionReturn(0);
44a1d52234SKris Buschelman }
45a1d52234SKris Buschelman 
46a1d52234SKris Buschelman #undef __FUNCT__
47a7bb0f05SBarry Smith #define __FUNCT__ "MatSeqAIJFromMatlab"
4884c105d7SBarry Smith /*@C
49e3c5b3baSBarry Smith     MatSeqAIJFromMatlab - Given a MATLAB sparse matrix, fills a SeqAIJ matrix with its transpose.
50a7bb0f05SBarry Smith 
51a7bb0f05SBarry Smith    Not Collective
52a7bb0f05SBarry Smith 
53a7bb0f05SBarry Smith    Input Parameters:
54e3c5b3baSBarry Smith +     mmat - a MATLAB sparse matris
55a7bb0f05SBarry Smith -     mat - a already created MATSEQAIJ
56a7bb0f05SBarry Smith 
572bd2b0e6SSatish Balay   Level: intermediate
582bd2b0e6SSatish Balay 
59a7bb0f05SBarry Smith @*/
60*8cc058d9SJed Brown PETSC_EXTERN PetscErrorCode MatSeqAIJFromMatlab(mxArray *mmat,Mat mat)
61a1d52234SKris Buschelman {
62dfbe8321SBarry Smith   PetscErrorCode ierr;
63b3da158bSBarry Smith   PetscInt       nz,n,m,*i,*j,k;
64b3da158bSBarry Smith   mwIndex        nnz,nn,nm,*ii,*jj;
65a1d52234SKris Buschelman   Mat_SeqAIJ     *aij = (Mat_SeqAIJ*)mat->data;
66a1d52234SKris Buschelman 
67a1d52234SKris Buschelman   PetscFunctionBegin;
68b3da158bSBarry Smith   nn  = mxGetN(mmat);   /* rows of transpose of matrix */
69b3da158bSBarry Smith   nm  = mxGetM(mmat);
70b3da158bSBarry Smith   nnz = (mxGetJc(mmat))[nn];
71b3da158bSBarry Smith   ii  = mxGetJc(mmat);
72b3da158bSBarry Smith   jj  = mxGetIr(mmat);
73b3da158bSBarry Smith   n   = (PetscInt) nn;
74b3da158bSBarry Smith   m   = (PetscInt) nm;
75b3da158bSBarry Smith   nz  = (PetscInt) nnz;
76b3da158bSBarry Smith 
77b3da158bSBarry Smith   if (mat->rmap->n < 0 && mat->cmap->n < 0) {
78b3da158bSBarry Smith     /* matrix has not yet had its size set */
79b3da158bSBarry Smith     ierr = MatSetSizes(mat,n,m,PETSC_DETERMINE,PETSC_DETERMINE);CHKERRQ(ierr);
804994cf47SJed Brown     ierr = MatSetUp(mat);CHKERRQ(ierr);
81b3da158bSBarry Smith   } else {
82b3da158bSBarry Smith     if (mat->rmap->n != n) SETERRQ2(PETSC_COMM_SELF,PETSC_ERR_SUP,"Cannot change size of PETSc matrix %D to %D",mat->rmap->n,n);
83b3da158bSBarry Smith     if (mat->cmap->n != m) SETERRQ2(PETSC_COMM_SELF,PETSC_ERR_SUP,"Cannot change size of PETSc matrix %D to %D",mat->cmap->n,m);
84b3da158bSBarry Smith   }
852eff7a51SBarry Smith   if (nz != aij->nz) {
862eff7a51SBarry Smith     /* number of nonzeros in matrix has changed, so need new data structure */
871f03425eSSatish Balay     ierr    = MatSeqXAIJFreeAIJ(mat,&aij->a,&aij->j,&aij->i);CHKERRQ(ierr);
88b3da158bSBarry Smith     aij->nz = nz;
89f0523c5fSHong Zhang     ierr    = PetscMalloc3(aij->nz,PetscScalar,&aij->a,aij->nz,PetscInt,&aij->j,mat->rmap->n+1,PetscInt,&aij->i);CHKERRQ(ierr);
902205254eSKarl Rupp 
91a1d52234SKris Buschelman     aij->singlemalloc = PETSC_TRUE;
922eff7a51SBarry Smith   }
93a1d52234SKris Buschelman 
94a1d52234SKris Buschelman   ierr = PetscMemcpy(aij->a,mxGetPr(mmat),aij->nz*sizeof(PetscScalar));CHKERRQ(ierr);
95e3c5b3baSBarry Smith   /* MATLAB stores by column, not row so we pass in the transpose of the matrix */
96b3da158bSBarry Smith   i = aij->i;
972205254eSKarl Rupp   for (k=0; k<n+1; k++) i[k] = (PetscInt) ii[k];
98b3da158bSBarry Smith   j = aij->j;
992205254eSKarl Rupp   for (k=0; k<nz; k++) j[k] = (PetscInt) jj[k];
100a1d52234SKris Buschelman 
1012205254eSKarl Rupp   for (k=0; k<mat->rmap->n; k++) aij->ilen[k] = aij->imax[k] = aij->i[k+1] - aij->i[k];
102a1d52234SKris Buschelman 
103a1d52234SKris Buschelman   ierr = MatAssemblyBegin(mat,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr);
104a1d52234SKris Buschelman   ierr = MatAssemblyEnd(mat,MAT_FINAL_ASSEMBLY);CHKERRQ(ierr);
105a7bb0f05SBarry Smith   PetscFunctionReturn(0);
106a7bb0f05SBarry Smith }
107a1d52234SKris Buschelman 
108a7bb0f05SBarry Smith #undef __FUNCT__
109a7bb0f05SBarry Smith #define __FUNCT__ "MatlabEngineGet_SeqAIJ"
1107087cfbeSBarry Smith PetscErrorCode  MatlabEngineGet_SeqAIJ(PetscObject obj,void *mengine)
111a7bb0f05SBarry Smith {
112a7bb0f05SBarry Smith   PetscErrorCode ierr;
113a7bb0f05SBarry Smith   Mat            mat = (Mat)obj;
114a7bb0f05SBarry Smith   mxArray        *mmat;
115a7bb0f05SBarry Smith 
116a7bb0f05SBarry Smith   PetscFunctionBegin;
117a7bb0f05SBarry Smith   mmat = engGetVariable((Engine*)mengine,obj->name);
118a7bb0f05SBarry Smith   ierr = MatSeqAIJFromMatlab(mmat,mat);CHKERRQ(ierr);
119a1d52234SKris Buschelman   PetscFunctionReturn(0);
120a1d52234SKris Buschelman }
121a1d52234SKris Buschelman 
12205db81ecSKris Buschelman #undef __FUNCT__
12305db81ecSKris Buschelman #define __FUNCT__ "MatSolve_Matlab"
124dfbe8321SBarry Smith PetscErrorCode MatSolve_Matlab(Mat A,Vec b,Vec x)
1253b3e256bSKris Buschelman {
126dfbe8321SBarry Smith   PetscErrorCode ierr;
127e060cb09SBarry Smith   const char     *_A,*_b,*_x;
1283b3e256bSKris Buschelman 
1293b3e256bSKris Buschelman   PetscFunctionBegin;
1303b3e256bSKris Buschelman   /* make sure objects have names; use default if not */
1313b3e256bSKris Buschelman   ierr = PetscObjectName((PetscObject)b);CHKERRQ(ierr);
1323b3e256bSKris Buschelman   ierr = PetscObjectName((PetscObject)x);CHKERRQ(ierr);
1333b3e256bSKris Buschelman 
1343b3e256bSKris Buschelman   ierr = PetscObjectGetName((PetscObject)A,&_A);CHKERRQ(ierr);
1353b3e256bSKris Buschelman   ierr = PetscObjectGetName((PetscObject)b,&_b);CHKERRQ(ierr);
1363b3e256bSKris Buschelman   ierr = PetscObjectGetName((PetscObject)x,&_x);CHKERRQ(ierr);
137ce94432eSBarry Smith   ierr = PetscMatlabEnginePut(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),(PetscObject)b);CHKERRQ(ierr);
138ce94432eSBarry Smith   ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"%s = u%s\\(l%s\\(p%s*%s));",_x,_A,_A,_A,_b);CHKERRQ(ierr);
139ce94432eSBarry Smith   ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"%s = 0;",_b);CHKERRQ(ierr);
140ce94432eSBarry Smith   /* ierr = PetscMatlabEnginePrintOutput(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),stdout);CHKERRQ(ierr);  */
141ce94432eSBarry Smith   ierr = PetscMatlabEngineGet(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),(PetscObject)x);CHKERRQ(ierr);
1423b3e256bSKris Buschelman   PetscFunctionReturn(0);
1433b3e256bSKris Buschelman }
1443b3e256bSKris Buschelman 
1453b3e256bSKris Buschelman #undef __FUNCT__
14605db81ecSKris Buschelman #define __FUNCT__ "MatLUFactorNumeric_Matlab"
1470481f469SBarry Smith PetscErrorCode MatLUFactorNumeric_Matlab(Mat F,Mat A,const MatFactorInfo *info)
1483b3e256bSKris Buschelman {
149dfbe8321SBarry Smith   PetscErrorCode ierr;
150de4209c5SBarry Smith   size_t         len;
1513b3e256bSKris Buschelman   char           *_A,*name;
152b3866ffcSBarry Smith   PetscReal      dtcol = info->dtcol;
1533b3e256bSKris Buschelman 
1543b3e256bSKris Buschelman   PetscFunctionBegin;
155d5f3da31SBarry Smith   if (F->factortype == MAT_FACTOR_ILU || info->dt > 0) {
156b3866ffcSBarry Smith     if (info->dtcol == PETSC_DEFAULT) dtcol = .01;
157fe97e370SBarry Smith     F->ops->solve = MatSolve_Matlab;
158d5f3da31SBarry Smith     F->factortype = MAT_FACTOR_LU;
1592205254eSKarl Rupp 
160ce94432eSBarry Smith     ierr = PetscMatlabEnginePut(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),(PetscObject)A);CHKERRQ(ierr);
161fe97e370SBarry Smith     _A   = ((PetscObject)A)->name;
162ce94432eSBarry Smith     ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"info_%s = struct('droptol',%g,'thresh',%g);",_A,info->dt,dtcol);CHKERRQ(ierr);
163ce94432eSBarry Smith     ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"[l_%s,u_%s,p_%s] = luinc(%s',info_%s);",_A,_A,_A,_A,_A);CHKERRQ(ierr);
164ce94432eSBarry Smith     ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"%s = 0;",_A);CHKERRQ(ierr);
165fe97e370SBarry Smith 
166fe97e370SBarry Smith     ierr = PetscStrlen(_A,&len);CHKERRQ(ierr);
167fe97e370SBarry Smith     ierr = PetscMalloc((len+2)*sizeof(char),&name);CHKERRQ(ierr);
168fe97e370SBarry Smith     sprintf(name,"_%s",_A);
169fe97e370SBarry Smith     ierr = PetscObjectSetName((PetscObject)F,name);CHKERRQ(ierr);
170fe97e370SBarry Smith     ierr = PetscFree(name);CHKERRQ(ierr);
171fe97e370SBarry Smith   } else {
172ce94432eSBarry Smith     ierr = PetscMatlabEnginePut(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),(PetscObject)A);CHKERRQ(ierr);
173f0523c5fSHong Zhang     _A   = ((PetscObject)A)->name;
174ce94432eSBarry Smith     ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"[l_%s,u_%s,p_%s] = lu(%s',%g);",_A,_A,_A,_A,dtcol);CHKERRQ(ierr);
175ce94432eSBarry Smith     ierr = PetscMatlabEngineEvaluate(PETSC_MATLAB_ENGINE_(PetscObjectComm((PetscObject)A)),"%s = 0;",_A);CHKERRQ(ierr);
1763b3e256bSKris Buschelman     ierr = PetscStrlen(_A,&len);CHKERRQ(ierr);
1773b3e256bSKris Buschelman     ierr = PetscMalloc((len+2)*sizeof(char),&name);CHKERRQ(ierr);
1783b3e256bSKris Buschelman     sprintf(name,"_%s",_A);
179f0523c5fSHong Zhang     ierr = PetscObjectSetName((PetscObject)F,name);CHKERRQ(ierr);
1803b3e256bSKris Buschelman     ierr = PetscFree(name);CHKERRQ(ierr);
1812205254eSKarl Rupp 
182f0523c5fSHong Zhang     F->ops->solve = MatSolve_Matlab;
183fe97e370SBarry Smith   }
1843b3e256bSKris Buschelman   PetscFunctionReturn(0);
1853b3e256bSKris Buschelman }
1863b3e256bSKris Buschelman 
1873b3e256bSKris Buschelman #undef __FUNCT__
18805db81ecSKris Buschelman #define __FUNCT__ "MatLUFactorSymbolic_Matlab"
1890481f469SBarry Smith PetscErrorCode MatLUFactorSymbolic_Matlab(Mat F,Mat A,IS r,IS c,const MatFactorInfo *info)
1903b3e256bSKris Buschelman {
1913b3e256bSKris Buschelman   PetscFunctionBegin;
192e32f2f54SBarry Smith   if (A->cmap->N != A->rmap->N) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_SIZ,"matrix must be square");
193f0523c5fSHong Zhang   F->ops->lufactornumeric = MatLUFactorNumeric_Matlab;
194b3866ffcSBarry Smith   F->assembled            = PETSC_TRUE;
1953b3e256bSKris Buschelman   PetscFunctionReturn(0);
1963b3e256bSKris Buschelman }
1973b3e256bSKris Buschelman 
19835bd34faSBarry Smith #undef __FUNCT__
19935bd34faSBarry Smith #define __FUNCT__ "MatFactorGetSolverPackage_seqaij_matlab"
20035bd34faSBarry Smith PetscErrorCode MatFactorGetSolverPackage_seqaij_matlab(Mat A,const MatSolverPackage *type)
20135bd34faSBarry Smith {
20235bd34faSBarry Smith   PetscFunctionBegin;
2032692d6eeSBarry Smith   *type = MATSOLVERMATLAB;
20435bd34faSBarry Smith   PetscFunctionReturn(0);
20535bd34faSBarry Smith }
20635bd34faSBarry Smith 
2073b3e256bSKris Buschelman #undef __FUNCT__
208b24902e0SBarry Smith #define __FUNCT__ "MatGetFactor_seqaij_matlab"
209*8cc058d9SJed Brown PETSC_EXTERN PetscErrorCode MatGetFactor_seqaij_matlab(Mat A,MatFactorType ftype,Mat *F)
2103b3e256bSKris Buschelman {
211dfbe8321SBarry Smith   PetscErrorCode ierr;
2123b3e256bSKris Buschelman 
2133b3e256bSKris Buschelman   PetscFunctionBegin;
214e32f2f54SBarry Smith   if (A->cmap->N != A->rmap->N) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_SIZ,"matrix must be square");
215ce94432eSBarry Smith   ierr                         = MatCreate(PetscObjectComm((PetscObject)A),F);CHKERRQ(ierr);
216f0523c5fSHong Zhang   ierr                         = MatSetSizes(*F,A->rmap->n,A->cmap->n,A->rmap->n,A->cmap->n);CHKERRQ(ierr);
2177adad957SLisandro Dalcin   ierr                         = MatSetType(*F,((PetscObject)A)->type_name);CHKERRQ(ierr);
2180298fd71SBarry Smith   ierr                         = MatSeqAIJSetPreallocation(*F,0,NULL);CHKERRQ(ierr);
219b24902e0SBarry Smith   (*F)->ops->lufactorsymbolic  = MatLUFactorSymbolic_Matlab;
220b3866ffcSBarry Smith   (*F)->ops->ilufactorsymbolic = MatLUFactorSymbolic_Matlab;
2212205254eSKarl Rupp 
22200de8ff0SBarry Smith   ierr = PetscObjectComposeFunction((PetscObject)(*F),"MatFactorGetSolverPackage_C","MatFactorGetSolverPackage_seqaij_matlab",MatFactorGetSolverPackage_seqaij_matlab);CHKERRQ(ierr);
223f0523c5fSHong Zhang 
224d5f3da31SBarry Smith   (*F)->factortype = ftype;
2253b3e256bSKris Buschelman   PetscFunctionReturn(0);
2263b3e256bSKris Buschelman }
227b24902e0SBarry Smith 
2283b3e256bSKris Buschelman /* --------------------------------------------------------------------------------*/
2293b3e256bSKris Buschelman 
23005db81ecSKris Buschelman #undef __FUNCT__
23105db81ecSKris Buschelman #define __FUNCT__ "MatFactorInfo_Matlab"
232dfbe8321SBarry Smith PetscErrorCode MatFactorInfo_Matlab(Mat A,PetscViewer viewer)
2333b3e256bSKris Buschelman {
234dfbe8321SBarry Smith   PetscErrorCode ierr;
2353b3e256bSKris Buschelman 
2363b3e256bSKris Buschelman   PetscFunctionBegin;
237e3c5b3baSBarry Smith   ierr = PetscViewerASCIIPrintf(viewer,"MATLAB run parameters:  -- not written yet!\n");CHKERRQ(ierr);
2383b3e256bSKris Buschelman   PetscFunctionReturn(0);
2393b3e256bSKris Buschelman }
2403b3e256bSKris Buschelman 
2413b3e256bSKris Buschelman #undef __FUNCT__
24205db81ecSKris Buschelman #define __FUNCT__ "MatView_Matlab"
243b2d3331aSBarry Smith PetscErrorCode MatView_Matlab(Mat A,PetscViewer viewer)
244b2d3331aSBarry Smith {
245dfbe8321SBarry Smith   PetscErrorCode    ierr;
246ace3abfcSBarry Smith   PetscBool         iascii;
24705db81ecSKris Buschelman   PetscViewerFormat format;
24805db81ecSKris Buschelman 
24905db81ecSKris Buschelman   PetscFunctionBegin;
250b24902e0SBarry Smith   ierr = MatView_SeqAIJ(A,viewer);CHKERRQ(ierr);
251251f4c67SDmitry Karpeev   ierr = PetscObjectTypeCompare((PetscObject)viewer,PETSCVIEWERASCII,&iascii);CHKERRQ(ierr);
25232077d6dSBarry Smith   if (iascii) {
25305db81ecSKris Buschelman     ierr = PetscViewerGetFormat(viewer,&format);CHKERRQ(ierr);
25405db81ecSKris Buschelman     if (format == PETSC_VIEWER_ASCII_FACTOR_INFO) {
25505db81ecSKris Buschelman       ierr = MatFactorInfo_Matlab(A,viewer);
25605db81ecSKris Buschelman     }
25705db81ecSKris Buschelman   }
25805db81ecSKris Buschelman   PetscFunctionReturn(0);
25905db81ecSKris Buschelman }
260f365a357SKris Buschelman 
26105db81ecSKris Buschelman 
26205db81ecSKris Buschelman /*MC
2632692d6eeSBarry Smith   MATSOLVERMATLAB - "matlab" - Providing direct solvers (LU and QR) and drop tolerance
264e3c5b3baSBarry Smith   based ILU factorization (ILUDT) for sequential matrices via the external package MATLAB.
26505db81ecSKris Buschelman 
26605db81ecSKris Buschelman 
26741c8de11SBarry Smith   Works with MATSEQAIJ matrices.
26805db81ecSKris Buschelman 
26905db81ecSKris Buschelman   Options Database Keys:
270e3c5b3baSBarry Smith . -pc_factor_mat_solver_type matlab - selects MATLAB to do the sparse factorization
27141c8de11SBarry Smith 
27205db81ecSKris Buschelman 
27305db81ecSKris Buschelman   Level: beginner
27405db81ecSKris Buschelman 
27505db81ecSKris Buschelman .seealso: PCLU
27641c8de11SBarry Smith 
27741c8de11SBarry Smith .seealso: PCFactorSetMatSolverPackage(), MatSolverPackage
27805db81ecSKris Buschelman M*/
27905db81ecSKris Buschelman 
280