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