1 // =============================================================================
2 // === SuiteSparseQR_qmult =====================================================
3 // =============================================================================
4 
5 // Applies Q in Householder form to a sparse or dense matrix X.  These functions
6 // use the Householder form in plain sparse column form, as returned by
7 // the SuiteSparseQR function.
8 //
9 // The result Y is sparse if X is sparse, or dense if X is dense.
10 // The sparse/dense cases are handled via overloaded functions.
11 //
12 //  method SPQR_QTX (0): Y = Q'*X
13 //  method SPQR_QX  (1): Y = Q*X
14 //  method SPQR_XQT (2): Y = X*Q'
15 //  method SPQR_XQ  (3): Y = X*Q
16 //
17 //  Q is held in its Householder form, as the mh-by-nh sparse matrix H,
18 //  a vector HTau of length nh, and a permutation vector HPinv of length mh.
19 //  mh is m for methods 0 and 1, and n for methods 2 and 3.  If HPinv is
20 //  NULL, the identity permutation is used.
21 
22 #include "spqr.hpp"
23 
24 // =============================================================================
25 // === SuiteSparseQR_qmult (dense) =============================================
26 // =============================================================================
27 
28 #define HCHUNK_DENSE 32        // FUTURE: make this an input parameter
29 
30 // returns Y of size m-by-n, or NULL on failure
SuiteSparseQR_qmult(int method,cholmod_sparse * H,cholmod_dense * HTau,Long * HPinv,cholmod_dense * Xdense,cholmod_common * cc)31 template <typename Entry> cholmod_dense *SuiteSparseQR_qmult
32 (
33     // inputs, not modified
34     int method,             // 0,1,2,3
35     cholmod_sparse *H,      // either m-by-nh or n-by-nh
36     cholmod_dense *HTau,    // size 1-by-nh
37     Long *HPinv,            // size mh, identity permutation if NULL
38     cholmod_dense *Xdense,  // size m-by-n with leading dimension ldx
39 
40     // workspace and parameters
41     cholmod_common *cc
42 )
43 {
44     cholmod_dense *Ydense ;
45     Entry *X, *Y, *X1, *Y1, *Z1, *Hx, *C, *V, *Z, *CV, *Tau ;
46     Long *Hp, *Hi, *Wi, *Wmap ;
47     Long i, k, zsize, nh, mh, vmax, hchunk, vsize, csize, cvsize, wisize, ldx,
48         m, n ;
49     int ok = TRUE ;
50 
51     // -------------------------------------------------------------------------
52     // get inputs
53     // -------------------------------------------------------------------------
54 
55     RETURN_IF_NULL_COMMON (NULL) ;
56     RETURN_IF_NULL (H, NULL) ;
57     RETURN_IF_NULL (HTau, NULL) ;
58     RETURN_IF_NULL (Xdense, NULL) ;
59     Long xtype = spqr_type <Entry> ( ) ;
60     RETURN_IF_XTYPE_INVALID (H, NULL) ;
61     RETURN_IF_XTYPE_INVALID (HTau, NULL) ;
62     RETURN_IF_XTYPE_INVALID (Xdense, NULL) ;
63     cc->status = CHOLMOD_OK ;
64 
65     Hp = (Long *) H->p ;
66     Hi = (Long *) H->i ;
67     Hx = (Entry *) H->x ;
68     nh = H->ncol ;
69     mh = H->nrow ;
70 
71     X = (Entry *) Xdense->x ;
72     m = Xdense->nrow ;
73     n = Xdense->ncol ;
74     ldx = Xdense->d ;
75     ASSERT (ldx >= m) ;
76 
77     if (method == SPQR_QTX || method == SPQR_QX)
78     {
79         // rows of H and X must be the same
80         if (mh != m)
81         {
82             ERROR (CHOLMOD_INVALID, "mismatched dimensions") ;
83             return (NULL) ;
84         }
85     }
86     else if (method == SPQR_XQT || method == SPQR_XQ)
87     {
88         // rows of H and columns of X must be the same
89         if (mh != n)
90         {
91             ERROR (CHOLMOD_INVALID, "mismatched dimensions") ;
92             return (NULL) ;
93         }
94     }
95     else
96     {
97         ERROR (CHOLMOD_INVALID, "invalid method") ;
98         return (NULL) ;
99     }
100 
101     // -------------------------------------------------------------------------
102     // allocate result Y
103     // -------------------------------------------------------------------------
104 
105     Ydense = cholmod_l_allocate_dense (m, n, m, xtype, cc) ;
106     if (cc->status < CHOLMOD_OK)
107     {
108         // out of memory
109         return (NULL) ;
110     }
111     Y = (Entry *) Ydense->x ;
112 
113     if (m == 0 || n == 0)
114     {
115         // nothing to do
116         return (Ydense) ;
117     }
118 
119     // -------------------------------------------------------------------------
120     // allocate workspace
121     // -------------------------------------------------------------------------
122 
123     Z = NULL ;
124     zsize = m*n ;
125     if (method == SPQR_QX || method ==  SPQR_XQT)
126     {
127         // Z is needed only for methods SPQR_QX and SPQR_XQT
128         Z = (Entry *) cholmod_l_malloc (zsize, sizeof (Entry), cc) ;
129     }
130 
131     hchunk = MIN (HCHUNK_DENSE, nh) ;
132     ok = spqr_happly_work (method, m, n, nh, Hp, hchunk, &vmax, &vsize, &csize);
133 
134     ASSERT (vmax <= mh) ;
135 
136     wisize = mh + vmax ;
137     Wi = (Long *) cholmod_l_malloc (wisize, sizeof (Long), cc) ;
138     Wmap = Wi + vmax ;              // Wmap is of size mh, Wi of size vmax
139 
140     if (cc->status < CHOLMOD_OK)
141     {
142         // out of memory; free workspace and result Y
143         cholmod_l_free_dense (&Ydense, cc) ;
144         cholmod_l_free (zsize,  sizeof (Entry), Z,  cc) ;
145         cholmod_l_free (wisize, sizeof (Long), Wi, cc) ;
146         return (NULL) ;
147     }
148 
149     if (method == SPQR_QX || method ==  SPQR_XQT)
150     {
151         // Z = X
152         Z1 = Z ;
153         X1 = X ;
154         for (k = 0 ; k < n ; k++)
155         {
156             for (i = 0 ; i < m ; i++)
157             {
158                 Z1 [i] = X1 [i] ;
159             }
160             X1 += ldx ;
161             Z1 += m ;
162         }
163     }
164 
165     for (i = 0 ; i < mh ; i++)
166     {
167         Wmap [i] = EMPTY ;
168     }
169 
170     // -------------------------------------------------------------------------
171     // allocate O(hchunk) workspace
172     // -------------------------------------------------------------------------
173 
174     // cvsize = csize + vsize ;
175     cvsize = spqr_add (csize, vsize, &ok) ;
176     CV = NULL ;
177 
178     if (ok)
179     {
180         CV = (Entry *) cholmod_l_malloc (cvsize, sizeof (Entry), cc) ;
181     }
182 
183     // -------------------------------------------------------------------------
184     // punt if out of memory
185     // -------------------------------------------------------------------------
186 
187     if (!ok || cc->status < CHOLMOD_OK)
188     {
189         // PUNT: out of memory; try again with hchunk = 1
190         cc->status = CHOLMOD_OK ;
191         ok = TRUE ;
192         hchunk = 1 ;
193         ok = spqr_happly_work (method, m, n, nh, Hp, hchunk,
194             &vmax, &vsize, &csize) ;
195         // note that vmax has changed, but wisize is left as-is
196         // cvsize = csize + vsize ;
197         cvsize = spqr_add (csize, vsize, &ok) ;
198         if (ok)
199         {
200             CV = (Entry *) cholmod_l_malloc (cvsize, sizeof (Entry), cc) ;
201         }
202         if (!ok || cc->status < CHOLMOD_OK)
203         {
204             // out of memory (or problem too large); free workspace and result Y
205             cholmod_l_free_dense (&Ydense, cc) ;
206             cholmod_l_free (zsize,  sizeof (Entry), Z,  cc) ;
207             cholmod_l_free (wisize, sizeof (Long), Wi, cc) ;
208             ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ;
209             return (NULL) ;
210         }
211     }
212 
213     // -------------------------------------------------------------------------
214     // split up workspace
215     // -------------------------------------------------------------------------
216 
217     C = CV ;            // size csize
218     V = C + csize ;     // size vsize
219 
220     // -------------------------------------------------------------------------
221     // Y = Q'*X, Q*X, X*Q, or X*Q'
222     // -------------------------------------------------------------------------
223 
224     PR (("Method %d m %ld n %ld X %p Y %p P %p\n", method, m, n, X, Y, HPinv)) ;
225     PR (("Hp %p Hi %p Hx %p\n", Hp, Hi, Hx)) ;
226     ASSERT (IMPLIES ((nh > 0), Hp != NULL && Hi != NULL && Hx != NULL)) ;
227     Tau = (Entry *) HTau->x ;
228     PR (("Tau %p\n", Tau)) ;
229     ASSERT (Tau != NULL) ;
230 
231     if (method == SPQR_QTX)
232     {
233 
234         // ---------------------------------------------------------------------
235         // Y = Q'*X
236         // ---------------------------------------------------------------------
237 
238         // Y (P,:) = X
239         X1 = X ;
240         Y1 = Y ;
241         for (k = 0 ; k < n ; k++)
242         {
243             for (i = 0 ; i < m ; i++)
244             {
245                 Y1 [HPinv ? HPinv [i] : i] = X1 [i] ;
246             }
247             X1 += ldx ;
248             Y1 += m ;
249         }
250 
251         // apply H to Y
252         spqr_happly (method, m, n, nh, Hp, Hi, Hx, Tau, Y,
253             vmax, hchunk, Wi, Wmap, C, V, cc) ;
254 
255     }
256     else if (method == SPQR_QX)
257     {
258 
259         // ---------------------------------------------------------------------
260         // Y = Q*X
261         // ---------------------------------------------------------------------
262 
263         // apply H to Z
264         spqr_happly (method, m, n, nh, Hp, Hi, Hx, Tau, Z,
265             vmax, hchunk, Wi, Wmap, C, V, cc) ;
266 
267         // Y = Z (P,:)
268         Z1 = Z ;
269         Y1 = Y ;
270         for (k = 0 ; k < n ; k++)
271         {
272             for (i = 0 ; i < m ; i++)
273             {
274                 Y1 [i] = Z1 [HPinv ? HPinv [i] : i] ;
275             }
276             Z1 += m ;
277             Y1 += m ;
278         }
279 
280     }
281     else if (method == SPQR_XQT)
282     {
283 
284         // ---------------------------------------------------------------------
285         // Y = X*Q'
286         // ---------------------------------------------------------------------
287 
288         // apply H to Z
289         spqr_happly (method, m, n, nh, Hp, Hi, Hx, Tau, Z,
290             vmax, hchunk, Wi, Wmap, C, V, cc) ;
291 
292         // Y = Z (:,P)
293         Y1 = Y ;
294         for (k = 0 ; k < n ; k++)
295         {
296             ASSERT (IMPLIES (HPinv != NULL, HPinv [k] >= 0 && HPinv [k] < n)) ;
297             Z1 = Z + (HPinv ? HPinv [k] : k) * m ;  // m = leading dim. of Z
298             for (i = 0 ; i < m ; i++)
299             {
300                 Y1 [i] = Z1 [i] ;
301             }
302             Y1 += m ;
303         }
304 
305     }
306     else if (method == SPQR_XQ)
307     {
308 
309         // ---------------------------------------------------------------------
310         // Y = X*Q
311         // ---------------------------------------------------------------------
312 
313         // Y (:,P) = X
314         X1 = X ;
315         for (k = 0 ; k < n ; k++)
316         {
317             ASSERT (IMPLIES (HPinv != NULL, HPinv [k] >= 0 && HPinv [k] < n)) ;
318             Y1 = Y + (HPinv ? HPinv [k] : k) * m ;  // m = leading dim. of Y
319             for (i = 0 ; i < m ; i++)
320             {
321                 Y1 [i] = X1 [i] ;
322             }
323             X1 += ldx ;
324         }
325 
326         // apply H to Y
327         spqr_happly (method, m, n, nh, Hp, Hi, Hx, Tau, Y,
328             vmax, hchunk, Wi, Wmap, C, V, cc) ;
329     }
330 
331     // -------------------------------------------------------------------------
332     // free workspace and return Y
333     // -------------------------------------------------------------------------
334 
335     cholmod_l_free (cvsize, sizeof (Entry), CV, cc) ;
336     cholmod_l_free (zsize,  sizeof (Entry), Z,  cc) ;
337     cholmod_l_free (wisize, sizeof (Long), Wi, cc) ;
338 
339     if (CHECK_BLAS_INT && !cc->blas_ok)
340     {
341         ERROR (CHOLMOD_INVALID, "problem too large for the BLAS") ;
342         cholmod_l_free_dense (&Ydense, cc) ;
343         return (NULL) ;
344     }
345 
346     return (Ydense) ;
347 }
348 
349 
350 // =============================================================================
351 
352 template cholmod_dense *SuiteSparseQR_qmult <double>
353 (
354     // inputs, not modified
355     int method,             // 0,1,2,3
356     cholmod_sparse *H,      // either m-by-nh or n-by-nh
357     cholmod_dense *HTau,    // size 1-by-nh
358     Long *HPinv,            // size mh
359     cholmod_dense *Xdense,  // size m-by-n with leading dimension ldx
360 
361     // workspace and parameters
362     cholmod_common *cc
363 ) ;
364 
365 // =============================================================================
366 
367 template cholmod_dense *SuiteSparseQR_qmult <Complex>
368 (
369     // inputs, not modified
370     int method,             // 0,1,2,3
371     cholmod_sparse *H,      // either m-by-nh or n-by-nh
372     cholmod_dense *HTau,    // size 1-by-nh
373     Long *HPinv,            // size mh
374     cholmod_dense *Xdense,  // size m-by-n with leading dimension ldx
375 
376     // workspace and parameters
377     cholmod_common *cc
378 ) ;
379 
380 
381 // =============================================================================
382 // === SuiteSparseQR_qmult (sparse) ============================================
383 // =============================================================================
384 
385 // Applies Q in Householder form to a sparse matrix X>
386 
387 #define XCHUNK 4            // FUTURE: make a parameter
388 #define HCHUNK_SPARSE 4     // FUTURE: make a parameter
389 
SuiteSparseQR_qmult(int method,cholmod_sparse * H,cholmod_dense * HTau,Long * HPinv,cholmod_sparse * Xsparse,cholmod_common * cc)390 template <typename Entry> cholmod_sparse *SuiteSparseQR_qmult
391 (
392     // inputs, not modified
393     int method,             // 0,1,2,3
394     cholmod_sparse *H,      // size m-by-nh or n-by-nh
395     cholmod_dense *HTau,    // size 1-by-nh
396     Long *HPinv,            // size mh, identity permutation if NULL
397     cholmod_sparse *Xsparse,
398 
399     // workspace and parameters
400     cholmod_common *cc
401 )
402 {
403     cholmod_sparse *Ysparse ;
404     Entry *W, *W1, *Hx, *Xx, *C, *V, *CVW, *Tau ;
405     Long *Hp, *Hi, *Xp, *Xi, *Wi, *Wmap ;
406     Long i, k, ny, k1, xchunk, p, k2, m, n, nh, vmax, hchunk, vsize,
407         csize, cvwsize, wsize, wisize ;
408     int ok = TRUE ;
409 
410     // -------------------------------------------------------------------------
411     // get inputs
412     // -------------------------------------------------------------------------
413 
414     RETURN_IF_NULL_COMMON (NULL) ;
415     RETURN_IF_NULL (H, NULL) ;
416     RETURN_IF_NULL (HTau, NULL) ;
417     RETURN_IF_NULL (Xsparse, NULL) ;
418     Long xtype = spqr_type <Entry> ( ) ;
419     RETURN_IF_XTYPE_INVALID (H, NULL) ;
420     RETURN_IF_XTYPE_INVALID (HTau, NULL) ;
421     RETURN_IF_XTYPE_INVALID (Xsparse, NULL) ;
422     cc->status = CHOLMOD_OK ;
423 
424     if (method == SPQR_QTX || method == SPQR_QX)
425     {
426         // rows of H and X must be the same
427         if (H->nrow != Xsparse->nrow)
428         {
429             ERROR (CHOLMOD_INVALID, "mismatched dimensions") ;
430             return (NULL) ;
431         }
432     }
433     else if (method == SPQR_XQT || method == SPQR_XQ)
434     {
435         // rows of H and columns of X must be the same
436         if (H->nrow != Xsparse->ncol)
437         {
438             ERROR (CHOLMOD_INVALID, "mismatched dimensions") ;
439             return (NULL) ;
440         }
441     }
442     else
443     {
444         ERROR (CHOLMOD_INVALID, "invalid method") ;
445         return (NULL) ;
446     }
447 
448     // -------------------------------------------------------------------------
449     // methods 2 or 3: Y = X*Q' = (Q*X')' or Y = X*Q = (Q'*X')'
450     // -------------------------------------------------------------------------
451 
452     if (method == SPQR_XQT || method == SPQR_XQ)
453     {
454         cholmod_sparse *XT, *YT ;
455         XT = cholmod_l_transpose (Xsparse, 2, cc) ;
456         YT = SuiteSparseQR_qmult <Entry>
457             ((method == SPQR_XQT) ? SPQR_QX : SPQR_QTX, H, HTau, HPinv, XT, cc);
458         cholmod_l_free_sparse (&XT, cc) ;
459         Ysparse = cholmod_l_transpose (YT, 2, cc) ;
460         cholmod_l_free_sparse (&YT, cc) ;
461         return (Ysparse) ;
462     }
463 
464     // -------------------------------------------------------------------------
465     // get H and X
466     // -------------------------------------------------------------------------
467 
468     Hp = (Long *) H->p ;
469     Hi = (Long *) H->i ;
470     Hx = (Entry *) H->x ;
471     m = H->nrow ;
472     nh = H->ncol ;
473 
474     Xp = (Long *) Xsparse->p ;
475     Xi = (Long *) Xsparse->i ;
476     Xx = (Entry *) Xsparse->x ;
477     n = Xsparse->ncol ;
478 
479     Tau = (Entry *) HTau->x ;
480 
481     // -------------------------------------------------------------------------
482     // allocate Long workspace
483     // -------------------------------------------------------------------------
484 
485     xchunk = MIN (XCHUNK, n) ;
486     hchunk = MIN (HCHUNK_SPARSE, nh) ;
487 
488     ok = spqr_happly_work (method, m, xchunk, nh, Hp, hchunk,
489         &vmax, &vsize, &csize) ;
490 
491     wisize = m + vmax ;
492     Wi = (Long *) cholmod_l_malloc (wisize, sizeof (Long), cc) ;
493     Wmap = Wi + vmax ;              // Wmap is of size m, Wi is of size vmax
494 
495     if (cc->status < CHOLMOD_OK)
496     {
497         // out of memory
498         return (NULL) ;
499     }
500 
501     for (i = 0 ; i < m ; i++)
502     {
503         Wmap [i] = EMPTY ;
504     }
505 
506     // -------------------------------------------------------------------------
507     // allocate O(xchunk + hchunk) workspace
508     // -------------------------------------------------------------------------
509 
510     // wsize = xchunk * m ;
511     wsize = spqr_mult (xchunk, m, &ok) ;
512 
513     // cvwsize = wsize + csize + vsize ;
514     cvwsize = spqr_add (wsize, csize, &ok) ;
515     cvwsize = spqr_add (cvwsize, vsize, &ok) ;
516     CVW = NULL ;
517 
518     if (ok)
519     {
520         CVW = (Entry *) cholmod_l_malloc (cvwsize, sizeof (Entry), cc) ;
521     }
522 
523     // -------------------------------------------------------------------------
524     // punt if out of memory
525     // -------------------------------------------------------------------------
526 
527     if (!ok || cc->status < CHOLMOD_OK)
528     {
529         // PUNT: out of memory; try again with xchunk = 1 and hchunk = 1
530         cc->status = CHOLMOD_OK ;
531         xchunk = 1 ;
532         hchunk = 1 ;
533         ok = spqr_happly_work (method, m, xchunk, nh, Hp, hchunk,
534             &vmax, &vsize, &csize) ;
535         wsize = m ;
536 
537         // cvwsize = wsize + csize + vsize ;
538         cvwsize = spqr_add (wsize, csize, &ok) ;
539         cvwsize = spqr_add (cvwsize, vsize, &ok) ;
540         if (ok)
541         {
542             CVW = (Entry *) cholmod_l_malloc (cvwsize, sizeof (Entry), cc) ;
543         }
544         if (!ok || cc->status < CHOLMOD_OK)
545         {
546             // still out of memory (or problem too large)
547             ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ;
548             cholmod_l_free (wisize, sizeof (Long), Wi, cc) ;
549             return (NULL) ;
550         }
551     }
552 
553     // -------------------------------------------------------------------------
554     // split up workspace
555     // -------------------------------------------------------------------------
556 
557     C = CVW ;                       // C is of size csize Entry's
558     V = C + csize ;                 // V is of size vsize Entry's
559     W = V + vsize ;                 // W is of size wsize Entry's
560 
561     // -------------------------------------------------------------------------
562     // allocate result Y
563     // -------------------------------------------------------------------------
564 
565     // Y is a sparse matrix of size m-ny-n with space for m+1 entries
566     Ysparse = cholmod_l_allocate_sparse (m, n, m+1, TRUE, TRUE, 0, xtype, cc) ;
567     if (cc->status < CHOLMOD_OK)
568     {
569         // out of memory
570         cholmod_l_free (cvwsize, sizeof (Entry), CVW, cc) ;
571         cholmod_l_free (wisize,  sizeof (Long),  Wi,  cc) ;
572         return (NULL) ;
573     }
574     ny = 0 ;
575 
576     // -------------------------------------------------------------------------
577     // methods 0 or 1: Y = Q'*X or Q*X
578     // -------------------------------------------------------------------------
579 
580     if (method == SPQR_QTX)
581     {
582 
583         // ---------------------------------------------------------------------
584         // Y = Q'*X
585         // ---------------------------------------------------------------------
586 
587         // apply in blocks of xchunk columns each
588         for (k1 = 0 ; k1 < n ; k1 += xchunk)
589         {
590             // scatter W:  W (P,0:3) = X (:, k:k+3)
591             W1 = W ;
592             k2 = MIN (k1+xchunk, n) ;
593             for (k = k1 ; k < k2 ; k++)
594             {
595                 // W1 = 0
596                 for (i = 0 ; i < m ; i++)
597                 {
598                     W1 [i] = 0 ;
599                 }
600                 for (p = Xp [k] ; p < Xp [k+1] ; p++)
601                 {
602                     i = Xi [p] ;
603                     W1 [HPinv ? HPinv [i] : i] = Xx [p] ;
604                 }
605                 W1 += m ;
606             }
607 
608             // apply H to W
609 
610             spqr_happly (method, m, k2-k1, nh, Hp, Hi, Hx, Tau, W,
611                 vmax, hchunk, Wi, Wmap, C, V, cc) ;
612 
613             // append W onto Y
614             W1 = W ;
615             for (k = k1 ; k < k2 ; k++)
616             {
617                 spqr_append (W1, NULL, Ysparse, &ny, cc) ;
618 
619                 if (cc->status < CHOLMOD_OK)
620                 {
621                     // out of memory
622                     cholmod_l_free_sparse (&Ysparse, cc) ;
623                     cholmod_l_free (cvwsize, sizeof (Entry), CVW, cc) ;
624                     cholmod_l_free (wisize,  sizeof (Long),  Wi,  cc) ;
625                     return (NULL) ;
626                 }
627 
628                 W1 += m ;
629             }
630         }
631 
632     }
633     else // if (method == SPQR_QX)
634     {
635 
636         // ---------------------------------------------------------------------
637         // Y = Q*X
638         // ---------------------------------------------------------------------
639 
640         // apply in blocks of xchunk columns each
641         for (k1 = 0 ; k1 < n ; k1 += xchunk)
642         {
643             // scatter W:  W (:,0:3) = X (:, k:k+3)
644             W1 = W ;
645             k2 = MIN (k1+xchunk, n) ;
646             for (k = k1 ; k < k2 ; k++)
647             {
648                 // W1 = 0
649                 for (i = 0 ; i < m ; i++)
650                 {
651                     W1 [i] = 0 ;
652                 }
653                 for (p = Xp [k] ; p < Xp [k+1] ; p++)
654                 {
655                     i = Xi [p] ;
656                     W1 [i] = Xx [p] ;
657                 }
658                 W1 += m ;
659             }
660 
661             // apply H to W
662             spqr_happly (method, m, k2-k1, nh, Hp, Hi, Hx, Tau, W,
663                 vmax, hchunk, Wi, Wmap, C, V, cc) ;
664 
665             // append W (P,:) onto Y
666             W1 = W ;
667             for (k = k1 ; k < k2 ; k++)
668             {
669                 spqr_append (W1, HPinv, Ysparse, &ny, cc) ;
670                 if (cc->status < CHOLMOD_OK)
671                 {
672                     // out of memory
673                     cholmod_l_free_sparse (&Ysparse, cc) ;
674                     cholmod_l_free (cvwsize, sizeof (Entry), CVW, cc) ;
675                     cholmod_l_free (wisize,  sizeof (Long),  Wi,  cc) ;
676                     return (NULL) ;
677                 }
678                 W1 += m ;
679             }
680         }
681     }
682 
683     // -------------------------------------------------------------------------
684     // free workspace and reduce Y in size so that nnz (Y) == nzmax (Y)
685     // -------------------------------------------------------------------------
686 
687     cholmod_l_free (cvwsize, sizeof (Entry), CVW, cc) ;
688     cholmod_l_free (wisize,  sizeof (Long),  Wi,  cc) ;
689     cholmod_l_reallocate_sparse (cholmod_l_nnz (Ysparse,cc), Ysparse, cc) ;
690 
691     if (CHECK_BLAS_INT && !cc->blas_ok)
692     {
693         ERROR (CHOLMOD_INVALID, "problem too large for the BLAS") ;
694         cholmod_l_free_sparse (&Ysparse, cc) ;
695         return (NULL) ;
696     }
697 
698     return (Ysparse) ;
699 }
700 
701 
702 // =============================================================================
703 
704 template cholmod_sparse *SuiteSparseQR_qmult <double>
705 (
706     // inputs, not modified
707     int method,                 // 0,1,2,3
708     cholmod_sparse *H,          // size m-by-nh or n-by-nh
709     cholmod_dense *HTau,        // size 1-by-nh
710     Long *HPinv,                // size mh
711     cholmod_sparse *Xsparse,
712 
713     // workspace and parameters
714     cholmod_common *cc
715 ) ;
716 
717 // =============================================================================
718 
719 template cholmod_sparse *SuiteSparseQR_qmult <Complex>
720 (
721     // inputs, not modified
722     int method,                 // 0,1,2,3
723     cholmod_sparse *H,          // size m-by-nh or n-by-nh
724     cholmod_dense *HTau,        // size 1-by-nh
725     Long *HPinv,                // size mh
726     cholmod_sparse *Xsparse,
727 
728     // workspace and parameters
729     cholmod_common *cc
730 ) ;
731