23
23
* provide routines to access the result of the decomposition L and its
24
24
* inverse
25
25
* @date January 20th 2024
26
- * CholeskyDecompGenDim allowed copying and moving, but did not provide
27
- * copy and move constructors or assignment operators, resulting in
28
- * incorrect code and potential memory corruption, if somebody tried to
29
- * copy/move a CholeskyDecompGenDim instance. Since we're in a C++17
30
- * world now, use std::unique_ptr<F[]> and std::make_unique to get rid of
31
- * explicit memory management in CholeskyDecompGenDim, and provide correct
32
- * code for copy construction and assignment ( the default constructor and
33
- * destructor, and the move construction and assignment code auto-generated
34
- * by the compiler are fine, and these methods are defaulted) .
26
+ * CholeskyDecompGenDim allowed copying and moving, but did not provide copy
27
+ * and move constructors or assignment operators, resulting in incorrect
28
+ * code and potential memory corruption, if somebody tried to copy/move a
29
+ * CholeskyDecompGenDim instance. Since we're in a C++17 world now, use the
30
+ * excellent movable std::vector<F> to get rid of explicit memory management
31
+ * in CholeskyDecompGenDim, and thus provide correct code for copy/move
32
+ * construction and assignment. Moreover, the provided releaseWorkspace
33
+ * method allows reuse of the allocated vector inside a CholeskyDecompGenDim
34
+ * to reduce the cost of repeated matrix decomposition .
35
35
*/
36
36
37
37
#include < cmath>
38
+ #include < vector>
38
39
#include < algorithm>
39
- #include < memory>
40
40
41
41
namespace ROOT {
42
42
@@ -103,7 +103,7 @@ template<class F, unsigned N> class CholeskyDecomp
103
103
* operator()(int i, int j) for access to elements)
104
104
*/
105
105
template <class M > CholeskyDecomp (const M& m) :
106
- fL( ), fOk(false )
106
+ fOk(false )
107
107
{
108
108
using CholeskyDecompHelpers::_decomposer;
109
109
fOk = _decomposer<F, N, M>()(fL , m);
@@ -121,7 +121,7 @@ template<class F, unsigned N> class CholeskyDecomp
121
121
* (i * (i + 1)) / 2 + j
122
122
*/
123
123
template <typename G> CholeskyDecomp (G* m) :
124
- fL(), fOk(false )
124
+ fOk(false )
125
125
{
126
126
using CholeskyDecompHelpers::_decomposer;
127
127
using CholeskyDecompHelpers::PackedArrayAdapter;
@@ -327,50 +327,29 @@ template<class F> class CholeskyDecompGenDim
327
327
// / lower triangular matrix L
328
328
/* * lower triangular matrix L, packed storage, with diagonal
329
329
* elements pre-inverted */
330
- std::unique_ptr<F[] > fL ;
330
+ std::vector<F > fL ;
331
331
// / flag indicating a successful decomposition
332
332
bool fOk = false ;
333
333
public:
334
- // move construction and assignment, and destruction do the right thing, so
335
- // use the versions that the compiler generates for us...
336
- CholeskyDecompGenDim (CholeskyDecompGenDim&&) = default ;
337
- CholeskyDecompGenDim& operator =(CholeskyDecompGenDim&&) = default ;
338
- ~CholeskyDecompGenDim () = default ;
339
-
340
- // / copy constructor
341
- CholeskyDecompGenDim (const CholeskyDecompGenDim& other)
342
- : fN (other.fN ), fL (std::make_unique<F[]>((fN * (fN + 1 )) / 2 )),
343
- fOk (other.fOk )
344
- {
345
- std::copy (other.fL , other.fL + (fN * (fN + 1 )) / 2 , fL );
346
- }
347
- // / (copy) assignment
348
- CholeskyDecompGenDim& operator =(const CholeskyDecompGenDim& other)
349
- {
350
- if (this != &other) {
351
- // check if fL is large enough to hold other, if not, reallocate
352
- if (fN < other.fN )
353
- fL = std::make_unique<F[]>((other.fN * (other.fN + 1 )) / 2 );
354
- fN = other.fN ;
355
- std::copy (other.fL , other.fL + (fN * (fN + 1 )) / 2 , fL );
356
- fOk = other.fOk ;
357
- }
358
- return *this ;
359
- }
360
-
361
334
// / perform a Cholesky decomposition
362
335
/* * perform a Cholesky decomposition of a symmetric positive
363
336
* definite matrix m
364
337
*
365
338
* this is the constructor to uses with an SMatrix (and objects
366
339
* that behave like an SMatrix in terms of using
367
340
* operator()(int i, int j) for access to elements)
341
+ *
342
+ * The optional wksp parameter that can be used to avoid (re-)allocations
343
+ * on repeated decompositions of the same size (by passing a workspace of
344
+ * size N * (N + 1), or reusing one returned from releaseWorkspace by a
345
+ * previous decomposition).
368
346
*/
369
- template <class M > CholeskyDecompGenDim (unsigned N, const M& m) :
370
- fN(N), fL(std::make_unique<F[]>(( fN * ( fN + 1 )) / 2 ))
347
+ template <class M > CholeskyDecompGenDim (unsigned N, const M& m, std::vector<F> wksp = {} ) :
348
+ fN(N), fL(std::move(wksp ))
371
349
{
372
350
using CholeskyDecompHelpers::_decomposerGenDim;
373
- fOk = _decomposerGenDim<F, M>()(fL .get (), m, fN );
351
+ if (fL .size () < fN * (fN + 1 ) / 2 ) fL .resize (fN * (fN + 1 ) / 2 );
352
+ fOk = _decomposerGenDim<F, M>()(fL .data (), m, fN );
374
353
}
375
354
376
355
// / perform a Cholesky decomposition
@@ -383,14 +362,60 @@ template<class F> class CholeskyDecompGenDim
383
362
* NOTE: the matrix is given in packed representation, matrix
384
363
* element m(i,j) (j <= i) is supposed to be in array element
385
364
* (i * (i + 1)) / 2 + j
365
+ *
366
+ * The optional wksp parameter that can be used to avoid (re-)allocations
367
+ * on repeated decompositions of the same size (by passing a workspace of
368
+ * size N * (N + 1), or reusing one returned from releaseWorkspace by a
369
+ * previous decomposition).
386
370
*/
387
- template <typename G> CholeskyDecompGenDim (unsigned N, G* m) :
388
- fN(N), fL(std::make_unique<F[]>(( fN * ( fN + 1 )) / 2 ))
371
+ template <typename G> CholeskyDecompGenDim (unsigned N, G* m, std::vector<F> wksp = {} ) :
372
+ fN(N), fL(std::move(wksp ))
389
373
{
390
374
using CholeskyDecompHelpers::_decomposerGenDim;
391
375
using CholeskyDecompHelpers::PackedArrayAdapter;
376
+ if (fL .size () < fN * (fN + 1 ) / 2 ) fL .resize (fN * (fN + 1 ) / 2 );
392
377
fOk = _decomposerGenDim<F, PackedArrayAdapter<G> >()(
393
- fL .get (), PackedArrayAdapter<G>(m), fN );
378
+ fL .data (), PackedArrayAdapter<G>(m), fN );
379
+ }
380
+
381
+ // / release the workspace of the decomposition for reuse
382
+ /* * release the workspace of the decomposition for reuse
383
+ *
384
+ * If you use CholeskyDecompGenDim repeatedly with the same size N, it is
385
+ * possible to avoid repeatedly allocating the internal workspace. The
386
+ * constructors take an optional wksp argument, and this routine can be
387
+ * called to get the workspace out of a decomposition when you are done
388
+ * with it.
389
+ *
390
+ * Please note that once you call releaseWorkspace, you cannot do further
391
+ * operations on the decomposition object (and this is indicated by having
392
+ * it return false when you call ok()).
393
+ *
394
+ * Here is an example snippet of (pseudo-)code which illustrates the use of
395
+ * releaseWorkspace for inverting the covariance matrices of a number of
396
+ * items (which must all be of the same size):
397
+ *
398
+ * @code
399
+ * std::vector<float> wksp;
400
+ * for (const auto& item: items) {
401
+ * CholeskyDecompGenDim decomp(item.cov().size(), item.cov(),
402
+ * std::move(wksp));
403
+ * if (decomp.ok()) {
404
+ * decomp.Invert(item.invcov());
405
+ * }
406
+ * wksp = std::move(decomp.releaseWorkspace());
407
+ * }
408
+ * @endcode
409
+ *
410
+ * In the above code snippet, only the first CholeskyDecompGenDim
411
+ * constructor call will allocate memory. Subsequent calls in the loop will
412
+ * reuse the buffer from the first iteration.
413
+ */
414
+ std::vector<F> releaseWorkspace ()
415
+ {
416
+ fN = 0 ;
417
+ fOk = false ;
418
+ return std::move (fL );
394
419
}
395
420
396
421
// / returns true if decomposition was successful
@@ -411,7 +436,7 @@ template<class F> class CholeskyDecompGenDim
411
436
template <class V > bool Solve (V& rhs) const
412
437
{
413
438
using CholeskyDecompHelpers::_solverGenDim;
414
- if (fOk ) _solverGenDim<F,V>()(rhs, fL .get (), fN );
439
+ if (fOk ) _solverGenDim<F,V>()(rhs, fL .data (), fN );
415
440
return fOk ;
416
441
}
417
442
@@ -424,7 +449,10 @@ template<class F> class CholeskyDecompGenDim
424
449
template <class M > bool Invert (M& m) const
425
450
{
426
451
using CholeskyDecompHelpers::_inverterGenDim;
427
- if (fOk ) _inverterGenDim<F,M>()(m, fL .get (), fN );
452
+ if (fOk ) {
453
+ auto wksp = fL ;
454
+ _inverterGenDim<F,M>()(m, fN , wksp.data ());
455
+ }
428
456
return fOk ;
429
457
}
430
458
@@ -443,8 +471,9 @@ template<class F> class CholeskyDecompGenDim
443
471
using CholeskyDecompHelpers::_inverterGenDim;
444
472
using CholeskyDecompHelpers::PackedArrayAdapter;
445
473
if (fOk ) {
474
+ auto wksp = fL ;
446
475
PackedArrayAdapter<G> adapted (m);
447
- _inverterGenDim<F,PackedArrayAdapter<G> >()(adapted, fL . get (), fN );
476
+ _inverterGenDim<F,PackedArrayAdapter<G> >()(adapted, fN , wksp. data () );
448
477
}
449
478
return fOk ;
450
479
}
@@ -458,7 +487,7 @@ template<class F> class CholeskyDecompGenDim
458
487
template <class M > bool getL (M& m) const
459
488
{
460
489
if (!fOk ) return false ;
461
- const F* L = fL .get ();
490
+ const F* L = fL .data ();
462
491
for (unsigned i = 0 ; i < fN ; ++i) {
463
492
// zero upper half of matrix
464
493
for (unsigned j = i + 1 ; j < fN ; ++j)
@@ -484,7 +513,7 @@ template<class F> class CholeskyDecompGenDim
484
513
template <typename G> bool getL (G* m) const
485
514
{
486
515
if (!fOk ) return false ;
487
- const F* L = fL .get ();
516
+ const F* L = fL .data ();
488
517
// copy L
489
518
for (unsigned i = 0 ; i < (fN * (fN + 1 )) / 2 ; ++i)
490
519
m[i] = L[i];
@@ -504,7 +533,7 @@ template<class F> class CholeskyDecompGenDim
504
533
template <class M > bool getLi (M& m) const
505
534
{
506
535
if (!fOk ) return false ;
507
- const F* L = fL .get ();
536
+ const F* L = fL .data ();
508
537
for (unsigned i = 0 ; i < fN ; ++i) {
509
538
// zero lower half of matrix
510
539
for (unsigned j = i + 1 ; j < fN ; ++j)
@@ -536,7 +565,7 @@ template<class F> class CholeskyDecompGenDim
536
565
template <typename G> bool getLi (G* m) const
537
566
{
538
567
if (!fOk ) return false ;
539
- const F* L = fL .get ();
568
+ const F* L = fL .data ();
540
569
// copy L
541
570
for (unsigned i = 0 ; i < (fN * (fN + 1 )) / 2 ; ++i)
542
571
m[i] = L[i];
@@ -626,18 +655,14 @@ namespace CholeskyDecompHelpers {
626
655
template <class F , class M > struct _inverterGenDim
627
656
{
628
657
// / method to do the inversion
629
- void operator ()(M& dst, const F* src, unsigned N ) const
658
+ void operator ()(M& dst, unsigned N, F* wksp ) const
630
659
{
631
- // make working copy
632
- auto l_ = std::make_unique<F[]>(N * (N + 1 ) / 2 );
633
- F* l = l_.get ();
634
- std::copy (src, src + ((N * (N + 1 )) / 2 ), l);
635
- // ok, next step: invert off-diagonal part of matrix
636
- F* base1 = &l[1 ];
660
+ // invert off-diagonal part of matrix
661
+ F* base1 = &wksp[1 ];
637
662
for (unsigned i = 1 ; i < N; base1 += ++i) {
638
663
for (unsigned j = 0 ; j < i; ++j) {
639
664
F tmp = F (0.0 );
640
- const F *base2 = &l [(i * (i - 1 )) / 2 ];
665
+ const F *base2 = &wksp [(i * (i - 1 )) / 2 ];
641
666
for (unsigned k = i; k-- > j; base2 -= k)
642
667
tmp -= base1[k] * base2[j];
643
668
base1[j] = tmp * base1[i];
@@ -648,7 +673,7 @@ namespace CholeskyDecompHelpers {
648
673
for (unsigned i = N; i--; ) {
649
674
for (unsigned j = i + 1 ; j--; ) {
650
675
F tmp = F (0.0 );
651
- base1 = &l [(N * (N - 1 )) / 2 ];
676
+ base1 = &wksp [(N * (N - 1 )) / 2 ];
652
677
for (unsigned k = N; k-- > i; base1 -= k)
653
678
tmp += base1[i] * base1[j];
654
679
dst (i, j) = tmp;
@@ -662,7 +687,12 @@ namespace CholeskyDecompHelpers {
662
687
{
663
688
// / method to do the inversion
664
689
void operator ()(M& dst, const F* src) const
665
- { return _inverterGenDim<F, M>()(dst, src, N); }
690
+ {
691
+ // make working copy
692
+ F wksp[N * (N + 1 ) / 2 ];
693
+ std::copy (src, src + ((N * (N + 1 )) / 2 ), wksp);
694
+ return _inverterGenDim<F, M>()(dst, N, wksp);
695
+ }
666
696
};
667
697
668
698
// / struct to solve a linear system using its Cholesky decomposition (generalised dimensionality)
0 commit comments