1 #ifndef SimTK_SimTKCOMMON_MATRIX_HELPER_REP_TRI_H_ 2 #define SimTK_SimTKCOMMON_MATRIX_HELPER_REP_TRI_H_ 3 4 /* -------------------------------------------------------------------------- * 5 * Simbody(tm): SimTKcommon * 6 * -------------------------------------------------------------------------- * 7 * This is part of the SimTK biosimulation toolkit originating from * 8 * Simbios, the NIH National Center for Physics-Based Simulation of * 9 * Biological Structures at Stanford, funded under the NIH Roadmap for * 10 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. * 11 * * 12 * Portions copyright (c) 2008-12 Stanford University and the Authors. * 13 * Authors: Michael Sherman * 14 * Contributors: * 15 * * 16 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 17 * not use this file except in compliance with the License. You may obtain a * 18 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 19 * * 20 * Unless required by applicable law or agreed to in writing, software * 21 * distributed under the License is distributed on an "AS IS" BASIS, * 22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 23 * See the License for the specific language governing permissions and * 24 * limitations under the License. * 25 * -------------------------------------------------------------------------- */ 26 27 #include "SimTKcommon/Scalar.h" 28 #include "SimTKcommon/SmallMatrix.h" 29 30 #include "MatrixHelperRep.h" 31 32 #include <cstddef> 33 34 namespace SimTK { 35 36 37 //--------------------------------- TriHelper ---------------------------------- 38 39 /// This abstract class represents a square matrix for which only half the elements 40 /// need to be stored in memory, and that half is either the lower or upper triangle. 41 /// The remaining half must have elements whose values can be determined from 42 /// the stored ones. There are several cases: 43 /// 44 /// - the missing elements are all zero (a triangular matrix) 45 /// - the missing (i,j)th element is the same as the stored (j,i)th element 46 /// (a positionally symmetric matrix) 47 /// - the missing (i,j)th element is the generalized hermitian transpose 48 /// of the stored (j,i)th element (a generalized hermitian matrix) 49 /// - the missing elements are the negatives of the above (skew-symmetric 50 /// or skew-hermitian matrix) 51 /// 52 /// In addition, we allow for the possibility that the diagonal 53 /// elements are all the same, known value and don't need to be stored; we'll 54 /// call that a "known diagonal" matrix. The known diagonal capability permits 55 /// two full triangle-shaped matrices to be packed in the space of a single 56 /// full matrix provided that at least one of them is a known diagonal matrix. 57 /// Typically the known diagonal value is one (unit diagonal), but it can have 58 /// other values (e.g. a skew symmetric matrix has a zero diagonal). 59 /// 60 /// Read-only references to any of the stored elements, and to the known diagonal 61 /// elements if any, can be obtained through the usual getElt_(i,j) virtual. 62 /// Writable references to the stored elements (but not a diagonal element of 63 /// a known diagonal matrix) can be obtained with the updElt_(i,j) virtual. 64 /// 65 /// In any matrix where the (i,j)th element must be derived 66 /// from the (j,i)th via some non-identity transformation, the diagonal 67 /// elements are required to be invariant under that transformation. For a 68 /// skew-symmetric matrix the diagonals must be zero to make m(i,j)==-m(i,j). 69 /// For a hermitian matrix, the diagonals must be real so that m(i,j)==conj(m(i,j)). 70 /// For a skew-hermitian matrix the diagonals must be imaginary so that 71 /// m(i,j)==-conj(m(i,j)). In general the diagonals must be recursively 72 /// self-hermitian-transposes for composite elements in hermitian matrices. 73 /// 74 /// Although the interesting part of the matrix is square, we allow that to be 75 /// the upper left square of a rectangular matrix, where all the additional 76 /// elements are zero (that's called "trapezoidal" although it might really 77 /// be a trapezoid because the triangle part may be flipped. In any case if 78 /// the matrix has dimension m X n, the leading square has dimension min(m,n). 79 80 //------------------------------------------------------------------------------ 81 template <class S> 82 class TriHelper : public MatrixHelperRep<S> { 83 typedef TriHelper<S> This; 84 typedef MatrixHelperRep<S> Base; 85 typedef typename CNT<S>::TNeg SNeg; 86 typedef typename CNT<S>::THerm SHerm; 87 typedef TriHelper<SNeg> ThisNeg; 88 typedef TriHelper<SHerm> ThisHerm; 89 public: TriHelper(int esz,int cppesz)90 TriHelper(int esz, int cppesz) : Base(esz,cppesz) {} 91 92 // Override in derived classes with assumed-unit diagonals. hasUnitDiagonal()93 virtual bool hasUnitDiagonal() const {return false;} 94 95 // Just changing the return type here. 96 virtual This* cloneHelper_() const = 0; 97 98 // A deep copy of a Tri matrix will always return another 99 // Tri matrix. 100 virtual This* createDeepCopy_() const = 0; 101 102 103 // TODO: this should allow at least views which are Triangular, meaning that 104 // the upper left corner should be on the diagonal. createBlockView_(const EltBlock & block)105 MatrixHelperRep<S>* createBlockView_(const EltBlock& block) { 106 SimTK_ERRCHK_ALWAYS(!"implemented", "TriHelper::createBlockView_()", "not implemented"); 107 return 0; 108 } 109 // TODO: the transpose has conjugate elements, which we would normally handle 110 // by typecasting to CNT<S>::THerm. However, here we're being asked to make a 111 // view without typecasting, so we have to do it with the elements we've got. 112 // No problem: turn a "lower" into an "upper"; the missing elements (the 113 // conjugated ones) will have moved to their proper locations. createTransposeView_()114 MatrixHelperRep<S>* createTransposeView_() { 115 SimTK_ERRCHK_ALWAYS(!"not implemented", "TriHelper::createTransposeView_()", "not implemented"); 116 return 0; 117 } 118 119 // Not sure if this should ever be supported. createRegularView_(const EltBlock &,const EltIndexer &)120 MatrixHelperRep<S>* createRegularView_(const EltBlock&, const EltIndexer&) { 121 SimTK_ERRCHK_ALWAYS(!"not implemented", "TriHelper::createRegularView_()", "not implemented"); 122 return 0; 123 } 124 createColumnView_(int,int,int)125 VectorHelper<S>* createColumnView_(int,int,int){ 126 SimTK_ERRCHK_ALWAYS(!"not implemented", "TriHelper::createColumnView_()", "not implemented"); 127 return 0; 128 } createRowView_(int,int,int)129 VectorHelper<S>* createRowView_(int,int,int){ 130 SimTK_ERRCHK_ALWAYS(!"not implemented", "TriHelper::createRowView_()", "not implemented"); 131 return 0; 132 } 133 protected: 134 }; 135 136 137 //----------------------------- TriInFullHelper -------------------------------- 138 /// We are assuming non-packed storage here: the triangle matrix is stored 139 /// in a full matrix so the stored elements are indexed exactly as they would 140 /// be if the matrix were full. Two triangle matrices can be stored 141 /// in the available memory, one in the lower triangle and one in the upper, 142 /// provided that at least one has known diagonal elements that don't need to be 143 /// stored (that's still not considered "packed"). 144 //------------------------------------------------------------------------------ 145 template <class S> 146 class TriInFullHelper : public TriHelper<S> { 147 typedef typename CNT<S>::StdNumber StdNumber; 148 typedef TriInFullHelper<S> This; 149 typedef TriHelper<S> Base; 150 public: 151 // If we're allocating the space, we'll just allocate a square even if the 152 // matrix is trapezoidal. TriInFullHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder)153 TriInFullHelper(int esz, int cppesz, int m, int n, 154 bool triangular, bool hermitian, bool skew, bool rowOrder) 155 : Base(esz,cppesz), m_minmn(std::min(m,n)), m_leadingDim(m_minmn*esz), 156 m_triangular(triangular), m_hermitian(hermitian), 157 m_skew(skew), m_rowOrder(rowOrder), m_unstored(new S[NumUnstored*esz]) 158 { 159 this->m_owner = true; 160 this->m_writable = true; 161 this->allocateData(m_minmn, m_minmn); 162 this->m_actual.setActualSize(m, n); // the apparent size 163 } 164 165 // Use someone else's memory, which we assume to be the right size. TriInFullHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder,int ldim,const S * shared,bool canWrite)166 TriInFullHelper(int esz, int cppesz, int m, int n, 167 bool triangular, bool hermitian, bool skew, bool rowOrder, 168 int ldim, const S* shared, bool canWrite) 169 : Base(esz,cppesz), m_minmn(std::min(m,n)), m_leadingDim(ldim), 170 m_triangular(triangular), m_hermitian(hermitian), 171 m_skew(skew), m_rowOrder(rowOrder), m_unstored(new S[NumUnstored*esz]) 172 { 173 assert(m_leadingDim >= m_minmn*this->m_eltSize); 174 this->m_owner = false; 175 this->m_writable = canWrite; 176 this->setData(const_cast<S*>(shared)); 177 this->m_actual.setActualSize(m, n); 178 } 179 ~TriInFullHelper()180 ~TriInFullHelper() {delete[] m_unstored;} 181 preferRowOrder_()182 bool preferRowOrder_() const {return m_rowOrder;} 183 184 // In full storage, Tri matrix elements are regularly spaced. hasRegularData_()185 bool hasRegularData_() const {return true;} 186 187 // Just changing the return type here: a cloned Tri-in-full-storage 188 // handle will always be another Tri-in-full-storage handle. 189 virtual This* cloneHelper_() const = 0; 190 191 // In full storage, a Tri matrix is not stored contiguously. hasContiguousData_()192 bool hasContiguousData_() const {return false;} 193 194 195 // Compute the missing elements. getAnyElt_(int i,int j,S * value)196 void getAnyElt_(int i, int j, S* value) const { 197 if (i==j || this->eltIsStored_(i,j)) { 198 this->copyElt(value, this->getElt_(i,j)); 199 return; 200 } 201 // Missing elements are all zero for triangular matrices, or if 202 // either dimension is outside the square part of any triangular-storage 203 // matrix (i.e., the matrix is trapezoidal). 204 if (m_triangular || i >= m_minmn || j >= m_minmn) { 205 this->fillElt(value, StdNumber(0)); 206 return; 207 } 208 209 // Symmetric or hermitian. The returned (i,j)th element is some 210 // function of the stored (j,i)th element. 211 const S* e = this->getElt_(j,i); 212 213 if (m_hermitian) { 214 if (m_skew) this->copyAndNegConjugateElt(value, e); 215 else this->copyAndConjugateElt(value, e); 216 } else { 217 // symmetric but not hermitian 218 if (m_skew) this->copyAndNegateElt(value, e); 219 else this->copyElt(value, e); // no change to stored element 220 } 221 } 222 223 VectorHelper<S>* createDiagonalView_(); 224 isUpper()225 virtual bool isUpper() const {return false;} hasKnownDiagonal()226 virtual bool hasKnownDiagonal() const {return false;} 227 createDeepCopy_()228 This* createDeepCopy_() const { 229 This* p = cloneHelper_(); 230 p->m_writable = true; 231 p->m_owner = true; 232 p->allocateData(m_minmn, m_minmn); 233 p->m_minmn = m_minmn; 234 p->m_leadingDim = m_minmn; // might be different than source 235 236 const bool shortFirst = (m_rowOrder&&!isUpper()) || (!m_rowOrder&&isUpper()); 237 const int known = hasKnownDiagonal() ? 1 : 0; 238 const int eltSize = this->m_eltSize; 239 240 // Copy 1/2 of a square of this size, possibly excluding diagonals. 241 const int nToCopy = m_minmn; 242 S* const dest = p->m_data + known*this->m_eltSize; // skip diag if known 243 const S* const src = this->m_data + known*this->m_eltSize; 244 if (shortFirst) { 245 // column or row begins at (k,j) and has length (j+1)-k 246 int lengthElt = eltSize; // 1st row/col has 1 element to copy 247 // skip 0-length row or col if diag is known 248 for (ptrdiff_t j=known; j < nToCopy; ++j, lengthElt += eltSize) { 249 std::copy(src+j*this->m_leadingDim, src+j*this->m_leadingDim + 250 lengthElt, dest+j*p->m_leadingDim); 251 } 252 } else { // longFirst 253 // column or row begins at (j+k,j), length m-j-k 254 int startInScalars = 0; // data was already shifted by 1 if needed 255 int lengthElt = (nToCopy-known)*eltSize; 256 for (ptrdiff_t j=0; j < nToCopy-known; ++j) { 257 std::copy(src+j*m_leadingDim + startInScalars, 258 src+j*m_leadingDim + startInScalars + lengthElt, 259 dest+j*p->m_leadingDim + startInScalars); 260 startInScalars += this->m_eltSize; 261 lengthElt -= eltSize; 262 } 263 } 264 return p; 265 } 266 267 // OK for any size elements. Note that we only allocate a square to hold 268 // the data even if the matrix is trapezoidal. The rest is known to be zero. 269 // There is no need to reallocate if min(m,n) doesn't change -- that just 270 // means more zeroes so the size will be changed. resize_(int m,int n)271 void resize_(int m, int n) { 272 const int newMinmn = std::min(m,n); 273 if (newMinmn == m_minmn) return; 274 this->clearData(); 275 m_minmn = newMinmn; 276 this->allocateData(m_minmn, m_minmn); 277 m_leadingDim = m_minmn*this->m_eltSize; // number of scalars in a column 278 } 279 280 // OK for any size elements. We'll move along the fast direction; columns 281 // for column-ordered and rows for row-ordered storage. There are two cases: 282 // (upper, col order) and (lower, row order) 283 // -- data starts at 0 for each column [row] and get longer (shortFirst) 284 // (upper, row order) and (lower, col order) 285 // -- data starts at the diagonal and gets shorter (longFirst) resizeKeep_(int m,int n)286 void resizeKeep_(int m, int n) { 287 const int newMinmn = std::min(m,n); 288 if (newMinmn == m_minmn) return; 289 290 const bool shortFirst = (m_rowOrder&&!isUpper()) || (!m_rowOrder&&isUpper()); 291 const int newLeadingDim = newMinmn; 292 const int known = hasKnownDiagonal() ? 1 : 0; 293 const int eltSize = this->m_eltSize; 294 295 // Copy 1/2 of a square of this size, possibly excluding diagonals. 296 S* const newData = this->allocateMemory(newMinmn, newMinmn); 297 const int nToCopy = std::min(m_minmn, newMinmn); 298 S* const dest = newData + known*this->m_eltSize; // skip diag if known 299 const S* const src = this->m_data + known*this->m_eltSize; 300 if (shortFirst) { 301 // column or row begins at (k,j) and has length (j+1)-k 302 int lengthElt = eltSize; // 1st row/col has 1 element to copy 303 // skip 0-length row or col if diag is known 304 for (ptrdiff_t j=known; j < nToCopy; ++j, lengthElt += eltSize) { 305 std::copy(src+j*this->m_leadingDim, src+j*this->m_leadingDim + 306 lengthElt, dest+j*newLeadingDim); 307 } 308 } else { // longFirst 309 // column or row begins at (j+k,j), length m-j-k 310 int startInScalars = 0; // data was already shifted by 1 if needed 311 int lengthElt = (nToCopy-known)*eltSize; 312 for (ptrdiff_t j=0; j < nToCopy-known; ++j) { 313 std::copy(src+j*this->m_leadingDim + startInScalars, 314 src+j*this->m_leadingDim + startInScalars + lengthElt, 315 dest+j*newLeadingDim + startInScalars); 316 startInScalars += this->m_eltSize; 317 lengthElt -= eltSize; 318 } 319 } 320 this->clearData(); 321 this->setData(newData); 322 m_minmn = newMinmn; 323 m_leadingDim = newLeadingDim; 324 } 325 326 protected: 327 int m_minmn; // dimension of the square part (min(m,n)) 328 int m_leadingDim; // in scalars; can be row- or column-ordered. 329 330 // These should be implicit in the subclasses but for now they're here 331 bool m_triangular; // true if triangular, false if symmetric or hermitian 332 bool m_hermitian; // m(i,j) = hermitianTranspose(m(j,i)) 333 bool m_skew; // m(i,j) = -op(m(j,i)) 334 bool m_rowOrder; 335 336 // Allocate heap space here for unstored elements. Don't forget to multiply 337 // by element size when accessing. 338 static const int NumUnstored = 3; // length is 3*element size in scalars 339 static const int UnstoredZero = 0; // first element holds a zero 340 static const int UnstoredDummy = 1; // second element is "write only" garbage 341 static const int UnstoredDiag = 2; // this is the known diagonal if any 342 S* m_unstored; 343 344 private: 345 346 }; 347 348 // Concrete class: Tri matrix in the upper triangle of full storage, 349 // diagonals are stored, composite elements. 350 // An upper triangle has i <= j. 351 template <class S> 352 class TriInFullUpperHelper : public TriInFullHelper<S> { 353 typedef TriInFullUpperHelper<S> This; 354 typedef TriInFullHelper<S> Base; 355 public: TriInFullUpperHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder)356 TriInFullUpperHelper(int esz, int cppesz, int m, int n, 357 bool triangular, bool hermitian, bool skew, bool rowOrder) 358 : Base(esz,cppesz,m,n,triangular,hermitian,skew,rowOrder) 359 { this->m_actual.setStructure(calcUpperTriStructure()); } 360 361 // Use someone else's memory, which we assume to be the right size. TriInFullUpperHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder,int ldim,const S * shared,bool canWrite)362 TriInFullUpperHelper(int esz, int cppesz, int m, int n, 363 bool triangular, bool hermitian, bool skew, bool rowOrder, 364 int ldim, const S* shared, bool canWrite) 365 : Base(esz,cppesz,m,n,triangular,hermitian,skew,rowOrder,ldim,shared,canWrite) 366 { this->m_actual.setStructure(calcUpperTriStructure()); } 367 isUpper()368 bool isUpper() const {return true;} 369 cloneHelper_()370 virtual This* cloneHelper_() const {return new This(*this);} 371 372 // override for unit diagonal eltIsStored_(int i,int j)373 virtual bool eltIsStored_(int i, int j) const {return i <= j && j < this->m_minmn;} 374 375 // override for unit diagonals and scalar elements getElt_(int i,int j)376 virtual const S* getElt_(int i, int j) const { 377 SimTK_ERRCHK2(i <= j && j < this->m_minmn, "TriInFullUpperHelper::getElt_()", 378 "Element index was (i,j)=(%d,%d) which refers to an element which is\n" 379 " not available. Only the upper triangle and diagonal of this matrix are stored.", 380 i, j); 381 if (this->m_rowOrder) std::swap(i,j); 382 return this->m_data + j*this->m_leadingDim + i*this->m_eltSize; 383 } 384 385 // override for unit diagonals and scalar elements updElt_(int i,int j)386 virtual S* updElt_(int i, int j) { 387 SimTK_ERRCHK2(i <= j && j < this->m_minmn, "TriInFullUpperHelper::updElt_()", 388 "Element index was (i,j)=(%d,%d) which refers to an element which is\n" 389 " not stored. Only the upper triangle and diagonal of this matrix are stored.", 390 i, j); 391 if (this->m_rowOrder) std::swap(i,j); 392 return this->m_data + j*this->m_leadingDim + i*this->m_eltSize; 393 } 394 395 private: calcUpperTriStructure()396 MatrixStructure calcUpperTriStructure() const { 397 MatrixStructure ms; 398 ms.setPosition(MatrixStructure::Upper); 399 ms.setDiagValue(this->hasKnownDiagonal() ? MatrixStructure::UnitDiag : MatrixStructure::StoredDiag); 400 if (this->m_triangular) ms.setStructure(MatrixStructure::Triangular); 401 else { 402 if (this->m_hermitian) 403 ms.setStructure(this->m_skew ? MatrixStructure::SkewHermitian 404 : MatrixStructure::Hermitian); 405 else // symmetric 406 ms.setStructure(this->m_skew ? MatrixStructure::SkewSymmetric 407 : MatrixStructure::Symmetric); 408 } 409 return ms; 410 } 411 }; 412 413 414 // Concrete class: Tri matrix in the upper triangle of full storage, 415 // diagonals are known, composite elements. 416 template <class S> 417 class TriInFullUpperKnownDiagHelper : public TriInFullUpperHelper<S> { 418 typedef TriInFullUpperKnownDiagHelper<S> This; 419 typedef TriInFullUpperHelper<S> Base; 420 public: TriInFullUpperKnownDiagHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder,const S * knownDiag)421 TriInFullUpperKnownDiagHelper(int esz, int cppesz, int m, int n, 422 bool triangular, bool hermitian, bool skew, bool rowOrder, const S* knownDiag) 423 : Base(esz,cppesz,m,n,triangular,hermitian,skew,rowOrder) 424 { 425 copyElt(&this->m_unknown[this->UnstoredDiag*this->m_eltSize], knownDiag); 426 } 427 TriInFullUpperKnownDiagHelper(int esz,int cppesz,int m,int n,bool triangular,bool hermitian,bool skew,bool rowOrder,const S * knownDiag,int ldim,const S * shared,bool canWrite)428 TriInFullUpperKnownDiagHelper(int esz, int cppesz, int m, int n, 429 bool triangular, bool hermitian, bool skew, bool rowOrder, const S* knownDiag, 430 int ldim, const S* shared, bool canWrite) 431 : Base(esz,cppesz,m,n,triangular,hermitian,skew,rowOrder,ldim,shared,canWrite) 432 { 433 copyElt(&this->m_unknown[this->UnstoredDiag*this->m_eltSize], knownDiag); 434 } 435 hasKnownDiagonal()436 bool hasKnownDiagonal() const {return true;} 437 cloneHelper_()438 This* cloneHelper_() const {return new This(*this);} 439 440 // We're overriding since only i<j is stored. eltIsStored_(int i,int j)441 bool eltIsStored_(int i, int j) const {return i<j && j < this->m_minmn;} 442 443 // These should be overwritten for scalars, although they will work as is. getElt_(int i,int j)444 virtual const S* getElt_(int i, int j) const { 445 SimTK_ERRCHK2(i<=j && j < this->m_minmn, "TriInFullUpperKnownDiagHelper::getElt_()", 446 "Element index (i,j)=(%d,%d) which refers to an element which is\n" 447 " not available. Only the upper triangle of this matrix is stored.", 448 i, j); 449 if (i==j) return &this->m_unknown[this->UnstoredDiag*this->m_eltSize]; 450 if (this->m_rowOrder) std::swap(i,j); 451 return this->m_data + j*this->m_leadingDim + i*this->m_eltSize; 452 } 453 454 // override for unit diagonals and scalar elements updElt_(int i,int j)455 virtual S* updElt_(int i, int j) { 456 SimTK_ERRCHK2(i<j && j < this->m_minmn, "TriInFullUpperKnownDiagHelper::updElt_()", 457 "Element index (i,j)=(%d,%d) which refers to the lower triangle, but\n" 458 " only the upper triangle (i<j) of this matrix are stored.", i, j); 459 if (this->m_rowOrder) std::swap(i,j); 460 return this->m_data + j*this->m_leadingDim + i*this->m_eltSize; 461 } 462 }; 463 464 465 466 467 468 } // namespace SimTK 469 470 471 #endif // SimTK_SimTKCOMMON_MATRIX_HELPER_REP_TRI_H_ 472