Matrix3<T> Matrix3<T>::Inverse( T determinant, const Matrix3 & adjoint ) const { if ( determinant == T() ) throw SingularMatrixException(); return adjoint * static_cast<T>( 1. / determinant ); }
inline void TrdtrmmUUnblocked( Orientation orientation, Matrix<F>& U ) { #ifndef RELEASE CallStackEntry entry("internal::TrdtrmmUUnblocked"); if( U.Height() != U.Width() ) LogicError("U must be square"); if( orientation == NORMAL ) LogicError("Trdtrmm requires (conjugate-)transpose"); #endif const Int n = U.Height(); F* UBuffer = U.Buffer(); const Int ldim = U.LDim(); for( Int j=0; j<n; ++j ) { const F delta11 = UBuffer[j+j*ldim]; if( delta11 == F(0) ) throw SingularMatrixException(); F* RESTRICT u01 = &UBuffer[j*ldim]; if( orientation == ADJOINT ) { // U00 := U00 + u01 (u01 / conj(delta11))^H for( Int k=0; k<j; ++k ) { const F gamma = Conj(u01[k]) / delta11; F* RESTRICT U00Col = &UBuffer[k*ldim]; for( Int i=0; i<=k; ++i ) U00Col[i] += u01[i]*gamma; } } else { // U00 := U00 + u01 (u01 / delta11)^T for( Int k=0; k<j; ++k ) { const F gamma = u01[k] / delta11; F* RESTRICT U00Col = &UBuffer[k*ldim]; for( Int i=0; i<=k; ++i ) U00Col[i] += u01[i]*gamma; } } // u01 := u01 / delta11 for( Int k=0; k<j; ++k ) u01[k] /= delta11; // lambda11 := 1 / delta11 UBuffer[j+j*ldim] = 1 / delta11; } }
inline void TrdtrmmLUnblocked( Orientation orientation, Matrix<F>& L ) { #ifndef RELEASE CallStackEntry entry("internal::TrdtrmmLUnblocked"); if( L.Height() != L.Width() ) LogicError("L must be square"); if( orientation == NORMAL ) LogicError("Trdtrmm requires (conjugate-)transpose"); #endif const Int n = L.Height(); F* LBuffer = L.Buffer(); const Int ldim = L.LDim(); for( Int j=0; j<n; ++j ) { const F delta11 = LBuffer[j+j*ldim]; if( delta11 == F(0) ) throw SingularMatrixException(); F* RESTRICT l10 = &LBuffer[j]; if( orientation == ADJOINT ) { // L00 := L00 + l10^H (l10 / delta11) for( Int k=0; k<j; ++k ) { const F gamma = l10[k*ldim] / delta11; F* RESTRICT L00Col = &LBuffer[k*ldim]; for( Int i=k; i<j; ++i ) L00Col[i] += Conj(l10[i*ldim])*gamma; } } else { // L00 := L00 + l10^T (l10 / delta11) for( Int k=0; k<j; ++k ) { const F gamma = l10[k*ldim] / delta11; F* RESTRICT L00Col = &LBuffer[k*ldim]; for( Int i=k; i<j; ++i ) L00Col[i] += l10[i*ldim]*gamma; } } // l10 := l10 / delta11 for( Int k=0; k<j; ++k ) l10[k*ldim] /= delta11; // lambda11 := 1 / delta11 LBuffer[j+j*ldim] = 1 / delta11; } }
inline void LU( Matrix<F>& A ) { #ifndef RELEASE PushCallStack("LU"); #endif // Matrix views Matrix<F> ATL, ATR, A00, a01, A02, alpha21T, ABL, ABR, a10, alpha11, a12, a21B, A20, a21, A22; PushBlocksizeStack( 1 ); PartitionDownDiagonal ( A, ATL, ATR, ABL, ABR, 0 ); while( ATL.Height() < A.Height() && ATL.Width() < A.Width() ) { RepartitionDownDiagonal ( ATL, /**/ ATR, A00, /**/ a01, A02, /*************/ /**********************/ /**/ a10, /**/ alpha11, a12, ABL, /**/ ABR, A20, /**/ a21, A22 ); //--------------------------------------------------------------------// F alpha = alpha11.Get(0,0); if( alpha == static_cast<F>(0) ) throw SingularMatrixException(); Scal( static_cast<F>(1)/alpha, a21 ); Geru( (F)-1, a21, a12, A22 ); //--------------------------------------------------------------------// SlidePartitionDownDiagonal ( ATL, /**/ ATR, A00, a01, /**/ A02, /**/ a10, alpha11, /**/ a12, /*************/ /**********************/ ABL, /**/ ABR, A20, a21, /**/ A22 ); } PopBlocksizeStack(); #ifndef RELEASE PopCallStack(); #endif }
inline void Unb( Matrix<F>& A ) { EL_DEBUG_CSE const Int m = A.Height(); const Int n = A.Width(); for( Int j=0; j<Min(m,n); ++j ) { const F alpha = A(j,j); if( alpha == F(0) ) throw SingularMatrixException(); blas::Scal( m-(j+1), F(1)/alpha, A.Buffer(j+1,j), 1 ); blas::Geru ( m-(j+1), n-(j+1), F(-1), A.LockedBuffer(j+1,j), 1, A.LockedBuffer(j,j+1), A.LDim(), A.Buffer(j+1,j+1), A.LDim() ); } }
inline void Unb( Matrix<F>& A ) { #ifndef RELEASE CallStackEntry entry("lu::Unb"); #endif const Int m = A.Height(); const Int n = A.Width(); for( Int j=0; j<Min(m,n); ++j ) { const F alpha = A.Get(j,j); if( alpha == F(0) ) throw SingularMatrixException(); blas::Scal( m-(j+1), 1/alpha, A.Buffer(j+1,j), 1 ); blas::Geru ( m-(j+1), n-(j+1), F(-1), A.LockedBuffer(j+1,j), 1, A.LockedBuffer(j,j+1), A.LDim(), A.Buffer(j+1,j+1), A.LDim() ); } }
inline void Trsm ( LeftOrRight side, UpperOrLower uplo, Orientation orientation, UnitOrNonUnit diag, F alpha, const Matrix<F>& A, Matrix<F>& B, bool checkIfSingular ) { #ifndef RELEASE PushCallStack("Trsm"); if( A.Height() != A.Width() ) throw std::logic_error("Triangular matrix must be square"); if( side == LEFT ) { if( A.Height() != B.Height() ) throw std::logic_error("Nonconformal Trsm"); } else { if( A.Height() != B.Width() ) throw std::logic_error("Nonconformal Trsm"); } #endif const char sideChar = LeftOrRightToChar( side ); const char uploChar = UpperOrLowerToChar( uplo ); const char transChar = OrientationToChar( orientation ); const char diagChar = UnitOrNonUnitToChar( diag ); if( checkIfSingular && diag != UNIT ) { const int n = A.Height(); for( int j=0; j<n; ++j ) if( A.Get(j,j) == F(0) ) throw SingularMatrixException(); } blas::Trsm ( sideChar, uploChar, transChar, diagChar, B.Height(), B.Width(), alpha, A.LockedBuffer(), A.LDim(), B.Buffer(), B.LDim() ); #ifndef RELEASE PopCallStack(); #endif }
inline void UnbFLAME( Matrix<F>& A ) { #ifndef RELEASE CallStackEntry entry("lu::UnbFLAME"); #endif // Matrix views Matrix<F> ATL, ATR, A00, a01, A02, alpha21T, ABL, ABR, a10, alpha11, a12, a21B, A20, a21, A22; PartitionDownDiagonal ( A, ATL, ATR, ABL, ABR, 0 ); while( ATL.Height() < A.Height() && ATL.Width() < A.Width() ) { RepartitionDownDiagonal ( ATL, /**/ ATR, A00, /**/ a01, A02, /*************/ /**********************/ /**/ a10, /**/ alpha11, a12, ABL, /**/ ABR, A20, /**/ a21, A22, 1 ); //--------------------------------------------------------------------// F alpha = alpha11.Get(0,0); if( alpha == F(0) ) throw SingularMatrixException(); Scale( 1/alpha, a21 ); Geru( F(-1), a21, a12, A22 ); //--------------------------------------------------------------------// SlidePartitionDownDiagonal ( ATL, /**/ ATR, A00, a01, /**/ A02, /**/ a10, alpha11, /**/ a12, /*************/ /**********************/ ABL, /**/ ABR, A20, a21, /**/ A22 ); } }
inline void UnbObj( Matrix<F>& A ) { EL_DEBUG_CSE const Int m = A.Height(); const Int n = A.Width(); const Int minDim = Min(m,n); for( Int k=0; k<minDim; ++k ) { const IR ind1( k ), ind2( k+1, END ); auto alpha11 = A( ind1, ind1 ); auto a12 = A( ind1, ind2 ); auto a21 = A( ind2, ind1 ); auto A22 = A( ind2, ind2 ); F alpha = alpha11(0); if( alpha == F(0) ) throw SingularMatrixException(); a21 *= 1/alpha; Geru( F(-1), a21, a12, A22 ); } }
void OCCSurface :: DefineTangentialPlane (const Point<3> & ap1, const PointGeomInfo & geominfo1, const Point<3> & ap2, const PointGeomInfo & geominfo2) { if (projecttype == PLANESPACE) { p1 = ap1; p2 = ap2; //cout << "p1 = " << p1 << endl; //cout << "p2 = " << p2 << endl; GetNormalVector (p1, geominfo1, ez); ex = p2 - p1; ex -= (ex * ez) * ez; ex.Normalize(); ey = Cross (ez, ex); GetNormalVector (p2, geominfo2, n2); nmid = 0.5*(n2+ez); ez = nmid; ez.Normalize(); ex = (p2 - p1).Normalize(); ez -= (ez * ex) * ex; ez.Normalize(); ey = Cross (ez, ex); nmid = ez; //cout << "ex " << ex << " ey " << ey << " ez " << ez << endl; } else { if ( (geominfo1.u < umin) || (geominfo1.u > umax) || (geominfo2.u < umin) || (geominfo2.u > umax) || (geominfo1.v < vmin) || (geominfo1.v > vmax) || (geominfo2.v < vmin) || (geominfo2.v > vmax) ) throw UVBoundsException(); p1 = ap1; p2 = ap2; psp1 = Point<2>(geominfo1.u, geominfo1.v); psp2 = Point<2>(geominfo2.u, geominfo2.v); Vec<3> n; GetNormalVector (p1, geominfo1, n); gp_Pnt pnt; gp_Vec du, dv; occface->D1 (geominfo1.u, geominfo1.v, pnt, du, dv); DenseMatrix D1(3,2), D1T(2,3), DDTinv(2,2); D1(0,0) = du.X(); D1(1,0) = du.Y(); D1(2,0) = du.Z(); D1(0,1) = dv.X(); D1(1,1) = dv.Y(); D1(2,1) = dv.Z(); /* (*testout) << "DefineTangentialPlane" << endl << "---------------------" << endl; (*testout) << "D1 = " << endl << D1 << endl; */ Transpose (D1, D1T); DenseMatrix D1TD1(3,3); D1TD1 = D1T*D1; if (D1TD1.Det() == 0) throw SingularMatrixException(); CalcInverse (D1TD1, DDTinv); DenseMatrix Y(3,2); Vec<3> y1 = (ap2-ap1).Normalize(); Vec<3> y2 = Cross(n, y1).Normalize(); for (int i = 0; i < 3; i++) { Y(i,0) = y1(i); Y(i,1) = y2(i); } DenseMatrix A(2,2); A = DDTinv * D1T * Y; DenseMatrix Ainv(2,2); if (A.Det() == 0) throw SingularMatrixException(); CalcInverse (A, Ainv); for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) { Amat(i,j) = A(i,j); Amatinv(i,j) = Ainv(i,j); } Vec<2> temp = Amatinv * (psp2-psp1); double r = temp.Length(); // double alpha = -acos (temp(0)/r); double alpha = -atan2 (temp(1),temp(0)); DenseMatrix R(2,2); R(0,0) = cos (alpha); R(1,0) = -sin (alpha); R(0,1) = sin (alpha); R(1,1) = cos (alpha); A = A*R; if (A.Det() == 0) throw SingularMatrixException(); CalcInverse (A, Ainv); for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) { Amat(i,j) = A(i,j); Amatinv(i,j) = Ainv(i,j); } temp = Amatinv * (psp2-psp1); }; }
inline void PanelLU ( DistMatrix<F, STAR,STAR>& A, DistMatrix<F, MC, STAR>& B, DistMatrix<int,STAR,STAR>& p, int pivotOffset ) { #ifndef RELEASE PushCallStack("internal::PanelLU"); if( A.Grid() != p.Grid() || p.Grid() != B.Grid() ) throw std::logic_error ("Matrices must be distributed over the same grid"); if( A.Width() != B.Width() ) throw std::logic_error("A and B must be the same width"); if( A.Height() != p.Height() || p.Width() != 1 ) throw std::logic_error("p must be a vector that conforms with A"); #endif const Grid& g = A.Grid(); const int r = g.Height(); const int colShift = B.ColShift(); const int colAlignment = B.ColAlignment(); // Matrix views DistMatrix<F,STAR,STAR> ATL(g), ATR(g), A00(g), a01(g), A02(g), ABL(g), ABR(g), a10(g), alpha11(g), a12(g), A20(g), a21(g), A22(g); DistMatrix<F,MC,STAR> BL(g), BR(g), B0(g), b1(g), B2(g); const int width = A.Width(); const int numBytes = (width+1)*sizeof(F)+sizeof(int); std::vector<byte> sendData(numBytes); std::vector<byte> recvData(numBytes); // Extract pointers to send and recv data // TODO: Think of how to make this safer with respect to alignment issues F* sendBufFloat = (F*)&sendData[0]; F* recvBufFloat = (F*)&recvData[0]; int* sendBufInt = (int*)&sendData[(width+1)*sizeof(F)]; int* recvBufInt = (int*)&recvData[(width+1)*sizeof(F)]; // Start the algorithm PushBlocksizeStack( 1 ); PartitionDownDiagonal ( A, ATL, ATR, ABL, ABR, 0 ); PartitionRight( B, BL, BR, 0 ); while( ATL.Height() < A.Height() ) { RepartitionDownDiagonal ( ATL, /**/ ATR, A00, /**/ a01, A02, /*************/ /**********************/ /**/ a10, /**/ alpha11, a12, ABL, /**/ ABR, A20, /**/ a21, A22 ); RepartitionRight ( BL, /**/ BR, B0, /**/ b1, B2 ); //--------------------------------------------------------------------// const int currentRow = a01.Height(); // Store the index/value of the pivot candidate in A F pivot = alpha11.GetLocal(0,0); int pivotRow = currentRow; for( int i=0; i<a21.Height(); ++i ) { F value = a21.GetLocal(i,0); if( FastAbs(value) > FastAbs(pivot) ) { pivot = value; pivotRow = currentRow + i + 1; } } // Update the pivot candidate to include local data from B for( int i=0; i<B.LocalHeight(); ++i ) { F value = b1.GetLocal(i,0); if( FastAbs(value) > FastAbs(pivot) ) { pivot = value; pivotRow = A.Height() + colShift + i*r; } } // Fill the send buffer with: // [ pivotValue | pivot row data | pivotRow ] if( pivotRow < A.Height() ) { sendBufFloat[0] = A.GetLocal(pivotRow,a10.Width()); const int ALDim = A.LocalLDim(); const F* ABuffer = A.LocalBuffer(pivotRow,0); for( int j=0; j<width; ++j ) sendBufFloat[j+1] = ABuffer[j*ALDim]; } else { const int localRow = ((pivotRow-A.Height())-colShift)/r; sendBufFloat[0] = b1.GetLocal(localRow,0); const int BLDim = B.LocalLDim(); const F* BBuffer = B.LocalBuffer(localRow,0); for( int j=0; j<width; ++j ) sendBufFloat[j+1] = BBuffer[j*BLDim]; } *sendBufInt = pivotRow; // Communicate to establish the pivot information mpi::AllReduce ( &sendData[0], &recvData[0], numBytes, PivotOp<F>(), g.ColComm() ); // Update the pivot vector pivotRow = *recvBufInt; p.SetLocal(currentRow,0,pivotRow+pivotOffset); // Copy the current row into the pivot row if( pivotRow < A.Height() ) { const int ALDim = A.LocalLDim(); F* ASetBuffer = A.LocalBuffer(pivotRow,0); const F* AGetBuffer = A.LocalBuffer(currentRow,0); for( int j=0; j<width; ++j ) ASetBuffer[j*ALDim] = AGetBuffer[j*ALDim]; } else { const int ownerRank = (colAlignment+(pivotRow-A.Height())) % r; if( g.Row() == ownerRank ) { const int localRow = ((pivotRow-A.Height())-colShift) / r; const int ALDim = A.LocalLDim(); const int BLDim = B.LocalLDim(); F* BBuffer = B.LocalBuffer(localRow,0); const F* ABuffer = A.LocalBuffer(currentRow,0); for( int j=0; j<width; ++j ) BBuffer[j*BLDim] = ABuffer[j*ALDim]; } } // Copy the pivot row into the current row { F* ABuffer = A.LocalBuffer(currentRow,0); const int ALDim = A.LocalLDim(); for( int j=0; j<width; ++j ) ABuffer[j*ALDim] = recvBufFloat[j+1]; } // Now we can perform the update of the current panel const F alpha = alpha11.GetLocal(0,0); if( alpha == F(0) ) throw SingularMatrixException(); const F alpha11Inv = F(1) / alpha; Scale( alpha11Inv, a21.LocalMatrix() ); Scale( alpha11Inv, b1.LocalMatrix() ); Geru( F(-1), a21.LocalMatrix(), a12.LocalMatrix(), A22.LocalMatrix() ); Geru( F(-1), b1.LocalMatrix(), a12.LocalMatrix(), B2.LocalMatrix() ); //--------------------------------------------------------------------// SlidePartitionDownDiagonal ( ATL, /**/ ATR, A00, a01, /**/ A02, /**/ a10, alpha11, /**/ a12, /*************/ /**********************/ ABL, /**/ ABR, A20, a21, /**/ A22 ); SlidePartitionRight ( BL, /**/ BR, B0, b1, /**/ B2 ); } PopBlocksizeStack(); #ifndef RELEASE PopCallStack(); #endif }
inline void PanelLU( Matrix<F>& A, Matrix<int>& p, int pivotOffset ) { #ifndef RELEASE PushCallStack("internal::PanelLU"); if( A.Width() != p.Height() || p.Width() != 1 ) throw std::logic_error("p must be a vector that conforms with A"); #endif // Matrix views Matrix<F> ATL, ATR, A00, a01, A02, ABL, ABR, a10, alpha11, a12, A20, a21, A22; const int width = A.Width(); std::vector<F> buffer( width ); // Start the algorithm PushBlocksizeStack( 1 ); PartitionDownDiagonal ( A, ATL, ATR, ABL, ABR, 0 ); while( ATL.Height() < A.Height() ) { RepartitionDownDiagonal ( ATL, /**/ ATR, A00, /**/ a01, A02, /*************/ /**********************/ /**/ a10, /**/ alpha11, a12, ABL, /**/ ABR, A20, /**/ a21, A22 ); //--------------------------------------------------------------------// const int currentRow = A00.Height(); // Find the index and value of the pivot candidate F pivot = alpha11.Get(0,0); int pivotRow = currentRow; for( int i=0; i<a21.Height(); ++i ) { const F value = a21.Get(i,0); if( FastAbs(value) > FastAbs(pivot) ) { pivot = value; pivotRow = currentRow + i + 1; } } p.Set( currentRow, 0, pivotRow+pivotOffset ); // Swap the pivot row and current row for( int j=0; j<width; ++j ) { buffer[j] = A.Get(currentRow,j); A.Set(currentRow,j,A.Get(pivotRow,j)); A.Set(pivotRow,j,buffer[j]); } // Now we can perform the update of the current panel const F alpha = alpha11.Get(0,0); if( alpha == F(0) ) throw SingularMatrixException(); const F alpha11Inv = F(1) / alpha; Scale( alpha11Inv, a21 ); Geru( F(-1), a21, a12, A22 ); //--------------------------------------------------------------------// SlidePartitionDownDiagonal ( ATL, /**/ ATR, A00, a01, /**/ A02, /**/ a10, alpha11, /**/ a12, /*************/ /**********************/ ABL, /**/ ABR, A20, a21, /**/ A22 ); } PopBlocksizeStack(); #ifndef RELEASE PopCallStack(); #endif }
Image<typename promote_trait<T, float>::TP> matrixInv(const Image<T>& M) { GVX_TRACE(__PRETTY_FUNCTION__); // NOTE: In the future if we need a faster matrix inverse, we could // specialize for double and float to use dgetri() and sgetri(), // respectively, from lapack. This would be analogous to the // specializations that we currently have for matrix-matrix and // vector-matrix multiplication in this file, as well as the // specializations for svd that we have in LinearAlgebra.C. ASSERT(M.isSquare()); typedef typename promote_trait<T, float>::TP TF; const int siz = M.getWidth(); // get an identity matrix: Image<TF> res = eye<TF>(siz); // make a promoted copy of M that we can modify: Image<TF> m(M); // for efficiency, we'll now pull pointers to the raw storage of our // mutable M copy, and of our result matrix -- this way, we can use // array indexing without all of the ASSERT()s in Image::getVal() // and Image::setVal() -- this turns out to cut the run time by // about a factor of 8x TF* m_arr = m.getArrayPtr(); TF* res_arr = res.getArrayPtr(); // ONLY USE THIS MACRO LOCALLY WITHIN THIS FUNCTION! It relies on // the local 'siz' variable, and on the fact that any array for // which it is called is a square matrix with dimensions siz*siz. #define ARR_IDX(arrptr, i, j) ((arrptr)[(i) + ((j)*siz)]) // let's pivote! for (int k = 0; k < siz; k ++) { // pivote at k: const int piv = matrixPivot(m, k); // could we pivote? if (piv == -1) throw SingularMatrixException(M, SRC_POS); // if we did pivote, then let's also exchange rows piv and k in temp: if (piv != k) for (int i = 0; i < siz; i ++) { std::swap(ARR_IDX(res_arr, i, piv), ARR_IDX(res_arr, i, k)); } // divide row k by our pivot value in both matrices: const TF val = 1.0F / ARR_IDX(m_arr, k, k); for (int i = 0; i < siz; i ++) { ARR_IDX(m_arr, i, k) *= val; ARR_IDX(res_arr, i, k) *= val; } // make sure everybody else in that column becomes zero in the // original matrix: for (int j = 0; j < siz; j ++) if (j != k) { const TF v = ARR_IDX(m_arr, k, j); for (int i = 0; i < siz; i ++) { ARR_IDX(m_arr, i, j) -= ARR_IDX(m_arr, i, k) * v; ARR_IDX(res_arr, i, j) -= ARR_IDX(res_arr, i, k) * v; } } } return res; #undef ARR_IDX }
inline void Var3Unb( Orientation orientation, Matrix<F>& A, Matrix<F>& d ) { #ifndef RELEASE PushCallStack("ldl::Var3Unb"); if( A.Height() != A.Width() ) throw std::logic_error("A must be square"); if( d.Viewing() && (d.Height() != A.Height() || d.Width() != 1) ) throw std::logic_error ("d must be a column vector the same height as A"); if( orientation == NORMAL ) throw std::logic_error("Can only perform LDL^T or LDL^H"); #endif const int n = A.Height(); if( !d.Viewing() ) d.ResizeTo( n, 1 ); F* ABuffer = A.Buffer(); F* dBuffer = d.Buffer(); const int ldim = A.LDim(); for( int j=0; j<n; ++j ) { const int a21Height = n - (j+1); // Extract and store the diagonal of D const F alpha11 = ABuffer[j+j*ldim]; if( alpha11 == F(0) ) throw SingularMatrixException(); dBuffer[j] = alpha11; F* RESTRICT a21 = &ABuffer[(j+1)+j*ldim]; if( orientation == ADJOINT ) { // A22 := A22 - a21 (a21 / alpha11)^H for( int k=0; k<a21Height; ++k ) { const F beta = Conj(a21[k]/alpha11); F* RESTRICT A22Col = &ABuffer[(j+1)+(j+1+k)*ldim]; for( int i=k; i<a21Height; ++i ) A22Col[i] -= a21[i]*beta; } } else { // A22 := A22 - a21 (a21 / alpha11)^T for( int k=0; k<a21Height; ++k ) { const F beta = a21[k]/alpha11; F* RESTRICT A22Col = &ABuffer[(j+1)+(j+1+k)*ldim]; for( int i=k; i<a21Height; ++i ) A22Col[i] -= a21[i]*beta; } } // a21 := a21 / alpha11 for( int i=0; i<a21Height; ++i ) a21[i] /= alpha11; } #ifndef RELEASE PopCallStack(); #endif }