.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Compat 3pm" .TH Compat 3pm 2024-05-17 "perl v5.38.2" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME PDL::CCS::Compat \- Backwards\-compatibility module for PDL::CCS .SH SYNOPSIS .IX Header "SYNOPSIS" .Vb 2 \& use PDL; \& use PDL::CCS::Compat; \& \& ##\-\- source pdl \& $a = random($N=8,$M=7); \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## Non\-missing value counts \& $nnz = $a\->flat\->nnz; ##\-\- "missing" == 0 \& $nnaz = $a\->flat\->nnza(1e\-6); ##\-\- "missing" ~= 0 \& #$ngood = $a\->ngood; ##\-\- "missing" == BAD (see PDL::Bad) \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## CCS Encoding \& ($ptr,$rowids,$vals) = ccsencode_nz ($a); # missing == 0 \& ($ptr,$rowids,$vals) = ccsencode_naz($a,$eps); # missing ~= 0 \& ($ptr,$rowids,$vals) = ccsencode_g ($a); # missing == BAD \& ($ptr,$rowids,$vals) = ccsencode_i ($i,$ivals,$N); # generic flat \& ($ptr,$rowids,$vals) = ccsencode_i2d($xi,$yi,$ivals); # generic 2d \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## CCS Decoding \& $cols = ccsdecodecols($ptr,$rowids,$nzvals, $xvals \& $a2 = ccsdecode ($ptr,$rowids,$vals); # missing == 0 \& $a2 = ccsdecode_g($ptr,$rowids,$vals); # missing == BAD \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## CCS Index Conversion \& $nzi = ccsitonzi ($ptr,$rowids, $ix, $missing); # ix => nzi \& $nzi = ccsi2dtonzi($ptr,$rowids, $xi,$yi, $missing); # 2d => nzi \& \& $ix = ccswhich ($ptr,$rowids,$vals); # CCS => ix \& ($xi,$yi) = ccswhichND($ptr,$rowids,$vals); # CCS => 2d \& $xyi = ccswhichND($ptr,$rowids,$vals); # ...as scalar \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## CCS Lookup \& \& $ixvals = ccsget ($ptr,$rowids,$vals, $ix,$missing); # ix => values \& $ixvals = ccsget2d($ptr,$rowids,$vals, $xi,$yi,$missing); # 2d => values \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## CCS Operations \& ($ptrT,$rowidsT,$valsT) = ccstranspose($ptr,$rowids,$vals); # CCS<\->CRS \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## Vector Operations, by column \& $nzvals_out = ccsadd_cv ($ptr,$rowids,$nzvals, $colvec); \& $nzvals_out = ccsdiff_cv($ptr,$rowids,$nzvals, $colvec); \& $nzvals_out = ccsmult_cv($ptr,$rowids,$nzvals, $colvec); \& $nzvals_out = ccsdiv_cv ($ptr,$rowids,$nzvals, $colvec); \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## Vector Operations, by row \& $nzvals_out = ccsadd_rv ($ptr,$rowids,$nzvals, $rowvec); \& $nzvals_out = ccsdiff_rv($ptr,$rowids,$nzvals, $rowvec); \& $nzvals_out = ccsmult_rv($ptr,$rowids,$nzvals, $rowvec); \& $nzvals_out = ccsdiv_rv ($ptr,$rowids,$nzvals, $rowvec); \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## Scalar Operations \& $nzvals_out = $nzvals * 42; # ... or whatever \& \& ##\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \& ## Accumulators \& $rowsumover = ccssumover ($ptr,$rowids,$nzvals); ##\-\- like $a\->sumover() \& $colsumovert = ccssumovert($ptr,$rowids,$nzvals); ##\-\- like $a\->xchg(0,1)\->sumover .Ve .SH Encoding .IX Header "Encoding" .SS ccs_encode_compat .IX Subsection "ccs_encode_compat" .Vb 3 \& Signature: (indx awhich(2,Nnz); avals(Nnz); \& indx $N; indx $M; \& indx [o]ptr(N); indx [o]rowids(Nnz); [o]nzvals(Nnz)) .Ve .PP Generic wrapper for backwards-compatible \fBccsencode()\fR variants. .SS ccsencode .IX Subsection "ccsencode" .SS ccsencode_nz .IX Subsection "ccsencode_nz" .Vb 1 \& Signature: (a(N,M); indx [o]ptr(N); indx [o]rowids(Nnz); [o]nzvals(Nnz)) .Ve .PP Encodes matrix $a() in compressed column format, interpreting zeroes as "missing" values. .PP Allocates output vectors if required. .SS ccsencodea .IX Subsection "ccsencodea" .SS ccsencode_naz .IX Subsection "ccsencode_naz" .Vb 1 \& Signature: (a(N,M); eps(); indx [o]ptr(N); indx [o]rowids(Nnz); [o]nzvals(Nnz)) .Ve .PP Encodes matrix $a() in CCS format interpreting approximate zeroes as "missing" values. This function is just like \fBccsencode_nz()\fR, but uses the tolerance parameter $\fBeps()\fR to determine which elements are to be treated as zeroes. .PP Allocates output vectors if required. .SS ccsencodeg .IX Subsection "ccsencodeg" .SS ccsencode_g .IX Subsection "ccsencode_g" .Vb 1 \& Signature: (a(N,M); indx [o]ptr(N); indx [o]rowids(Nnz); [o]nzvals(Nnz)) .Ve .PP Encodes matrix $a() in CCS format interpreting BAD values as "missing". Requires bad-value support built into PDL. .PP Allocates output vectors if required. .SS ccsencode_i .IX Subsection "ccsencode_i" .Vb 1 \& Signature: (indx ix(Nnz); nzvals(Nnz); indx $N; int [o]ptr(N); indx [o]rowids(Nnz); [o]nzvals_enc(Nnz)) .Ve .PP General-purpose CCS encoding method for flat indices. Encodes values $\fBnzvals()\fR from flat-index locations $\fBix()\fR into a CCS matrix ($\fBptr()\fR, $\fBrowids()\fR, $\fBnzvals_enc()\fR). .PP Allocates output vectors if required. .PP \&\f(CW$N\fR (~ \f(CW$a\fR\->\fBdim\fR\|(0)) must be specified. .SS ccsencode_i2d .IX Subsection "ccsencode_i2d" .Vb 9 \& Signature: ( \& indx xvals(Nnz) ; \& indx yvals(Nnz) ; \& nzvals(Nnz) ; \& indx $N ; ##\-\- optional \& indx [o]ptr(N) ; \& indx [o]rowids(Nnz) ; \& [o]nzvals_enc(Nnz); \& ) .Ve .PP General-purpose encoding method. Encodes values $\fBnzvals()\fR from 2d\-index locations ($\fBxvals()\fR, $\fByvals()\fR) in an \f(CW$N\fR\-by\-(whatever) PDL into a CCS matrix $\fBptr()\fR, $\fBrowids()\fR, $\fBnzvals_enc()\fR. .PP Allocates output vectors if required. If \f(CW$N\fR is omitted, it defaults to the maximum column index given in $\fBxvals()\fR. .SH Decoding .IX Header "Decoding" .SS ccsdecodecols .IX Subsection "ccsdecodecols" .Vb 9 \& Signature: ( \& indx ptr (N) ; \& indx rowids (Nnz); \& nzvals (Nnz); \& indx xvals (I) ; # default=sequence($N) \& missing() ; # default=0 \& M () ; # default=rowids\->max+1 \& [o]cols (I,M); # default=new \& ) .Ve .PP Extract dense columns from a CCS-encoded matrix (no dataflow). Allocates output matrix if required. If $a(N,M) was the dense source matrix for the CCS-encoding, and if missing values are zeros, then the following two calls are equivalent (modulo data flow): .PP .Vb 2 \& $cols = $a\->dice_axis(1,$col_ix); \& $cols = ccsdecodecols($ptr,$rowids,$nzvals, $col_ix,0); .Ve .SS ccsdecode .IX Subsection "ccsdecode" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); $M; [o]dense(N,M)) .Ve .PP Decodes compressed column format vectors $\fBptr()\fR, $\fBrowids()\fR, and $\fBnzvals()\fR into dense output matrix $a(). Allocates the output matrix if required. .PP Note that if the original matrix (pre-encoding) contained trailing rows with no nonzero elements, such rows will not be allocated by this method (unless you specify either \f(CW$M\fR or \f(CW$dense\fR). In such cases, you might prefer to call \fBccsdecodecols()\fR directly. .SS ccsdecode_g .IX Subsection "ccsdecode_g" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); $M; [o]dense(N,M)) .Ve .PP Convenience method. Like \fBccsdecode()\fR but sets "missing" values to BAD. .SH "Index Conversion" .IX Header "Index Conversion" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); indx ind(2,I); indx missing(); indx [o]nzix(I)) .Ve .SS ccsiNDtonzi .IX Subsection "ccsiNDtonzi" Convert N\-dimensional index values $\fBind()\fR appropriate for a dense matrix (N,M) into indices $\fBnzix()\fR appropriate for the $\fBrowids()\fR and/or $\fBnzvals()\fR components of the CCS-encoded matrix ($\fBptr()\fR,$\fBrowids()\fR,$\fBnzvals()\fR). Missing values are returned in $\fBnzix()\fR as $\fBmissing()\fR. .SS ccsi2dtonzi .IX Subsection "ccsi2dtonzi" .Vb 1 \& Signaure: (indx ptr(N); indx rowids(Nnz); indx col_ix(I); indx row_ix(I); indx missing(); indx [o]nzix(I)) .Ve .PP Convert 2d index values $\fBcol_ix()\fR and $\fBrow_ix()\fR appropriate for a dense matrix (N,M) into indices $\fBnzix()\fR appropriate for the $\fBrowids()\fR and/or $\fBnzvals()\fR components of the CCS-encoded matrix ($\fBptr()\fR,$\fBrowids()\fR,$\fBnzvals()\fR). Missing values are returned in $\fBnzix()\fR as $\fBmissing()\fR. .PP .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); indx ix(I); indx missing(); indx [o]nzix(I)) .Ve .SS ccsitonzi .IX Subsection "ccsitonzi" Convert flat index values $\fBix()\fR appropriate for a dense matrix (N,M) into indices $\fBnzix()\fR appropriate for the $\fBrowids()\fR and/or $\fBnzvals()\fR components of the CCS-encoded matrix ($\fBptr()\fR,$\fBrowids()\fR,$\fBnzvals()\fR). Missing values are returned in $\fBnzix()\fR as $\fBmissing()\fR. .SS ccswhichND .IX Subsection "ccswhichND" .SS ccswhich2d .IX Subsection "ccswhich2d" .SS ccswhichfull .IX Subsection "ccswhichfull" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); indx [o]which_cols(Nnz); indx [o]which_rows(Nnz)\*(Aq, .Ve .PP In scalar context, returns concatenation of $\fBwhich_cols()\fR and $\fBwhich_rows()\fR, similar to the builtin \fBwhichND()\fR. Note however that \fBccswhichND()\fR may return its index PDLs sorted in a different order than the builtin \fBwhichND()\fR method for dense matrices. Use the \fBqsort()\fR or \fBqsorti()\fR methods if you need sorted index PDLs. .SS ccswhich .IX Subsection "ccswhich" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); indx [o]which(Nnz); indx [t]wcols(Nnz)\*(Aq, .Ve .PP Convenience method. Calls \fBccswhichfull()\fR, and scales the output PDLs to correspond to a flat enumeration. The output PDL $\fBwhich()\fR is \fBnot\fR guaranteed to be sorted in any meaningful order. Use the \fBqsort()\fR method if you need sorted output. .SS ccstranspose .IX Subsection "ccstranspose" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); indx [o]ptrT(M); indx [o]rowidsT(Nnz); [o]nzvalsT(Nnz)\*(Aq, .Ve .PP Transpose a compressed matrix. .SH Lookup .IX Header "Lookup" .SS ccsget2d .IX Subsection "ccsget2d" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); indx xvals(I); indx yvals(I); missing(); [o]ixvals(I)) .Ve .PP Lookup values in a CCS-encoded PDL by 2d source index (no dataflow). Pretty much like \fBccsi2dtonzi()\fR, but returns values instead of indices. If you know that your index PDLs $\fBxvals()\fR and $\fByvals()\fR do not refer to any missing values in the CCS-encoded matrix, then the following two calls are equivalent (modulo dataflow): .PP .Vb 2 \& $ixvals = ccsget2d ($ptr,$rowids,$nzvals, $xvals,$yvals,0); \& $ixvals = index($nzvals, ccsi2dtonzi($ptr,$rowids, $xvals,$yvals,0)); .Ve .PP The difference is that only the second incantation will cause subsequent changes to \f(CW$ixvals\fR to be propagated back into \f(CW$nzvals\fR. .SS ccsget .IX Subsection "ccsget" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals(Nnz); indx ix(I); missing(); [o]ixvals(I)) .Ve .PP Lookup values in a CCS-encoded PDL by flat source index (no dataflow). Pretty much like \fBccsitonzi()\fR, but returns values instead of indices. If you know that your index PDL $\fBix()\fR does not refer to any missing values in the CCS-encoded matrix, then the following two calls are equivalent (modulo dataflow): .PP .Vb 2 \& $ixvals = ccsget ($ptr,$rowids,$nzvals, $ix,0); \& $ixvals = index($nzvals, ccsitonzi($ptr,$rowids, $ix,0)) .Ve .PP The difference is that only the second incantation will cause subsequent changes to \f(CW$ixvals\fR to be propagated back into \f(CW$nzvals\fR. .SH "Vector Operations" .IX Header "Vector Operations" .SS ccs${OP}_cv .IX Subsection "ccs${OP}_cv" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals_in(Nnz); colvec(M); [o]nzvals_out(Nnz)) .Ve .PP Column-vector operation ${OP} on CCS-encoded PDL. .PP Should do something like the following (without decoding the CCS matrix): .PP .Vb 1 \& ($colvec ${OP} ccsdecode(\e$ptr,\e$rowids,\e$nzvals))\->ccsencode; .Ve .PP Missing values in the CCS-encoded PDL are not affected by this operation. .PP ${OP} is one of the following: .PP .Vb 6 \& plus ##\-\- addition (alias: \*(Aqadd\*(Aq) \& minus ##\-\- subtraction (alias: \*(Aqdiff\*(Aq) \& mult ##\-\- multiplication (NOT matrix\-multiplication) \& divide ##\-\- division (alias: \*(Aqdiv\*(Aq) \& modulo ##\-\- modulo \& power ##\-\- potentiation \& \& gt ##\-\- greater\-than \& ge ##\-\- greater\-than\-or\-equal \& lt ##\-\- less\-than \& le ##\-\- less\-than\-or\-equal \& eq ##\-\- equality \& ne ##\-\- inequality \& spaceship ##\-\- 3\-way comparison \& \& and2 ##\-\- binary AND \& or2 ##\-\- binary OR \& xor ##\-\- binary XOR \& shiftleft ##\-\- left\-shift \& shiftright ##\-\- right\-shift .Ve .SS ccs${OP}_rv .IX Subsection "ccs${OP}_rv" .Vb 1 \& Signature: (indx ptr(N); indx rowids(Nnz); nzvals_in(Nnz); rowvec(N); [o]nzvals_out(Nnz)) .Ve .PP Row-vector operation ${OP} on CCS-encoded PDL. Should do something like the following (without decoding the CCS matrix): .PP .Vb 1 \& ($column\->slice("*1,") ${OP} ccsdecode($ptr,$rowids,$nzvals))\->ccsencode; .Ve .PP Missing values in the CCS-encoded PDL are not effected by this operation. .PP See ccs${OP}\fB_cv()\fR above for supported operations. .SH EXAMPLES .IX Header "EXAMPLES" .SS "Compressed Column Format Example" .IX Subsection "Compressed Column Format Example" .Vb 8 \& $a = pdl([ \& [10, 0, 0, 0,\-2, 0], \& [3, 9, 0, 0, 0, 3], \& [0, 7, 8, 7, 0, 0], \& [3, 0, 8, 7, 5, 0], \& [0, 8, 0, 9, 9, 13], \& [0, 4, 0, 0, 2, \-1] \& ]); \& \& ($ptr,$rowids,$nzvals) = ccsencode($a); \& \& print join("\en", "ptr=$ptr", "rowids=$rowids", "nzvals=$nzvals"); .Ve .PP \&... prints something like: .PP .Vb 3 \& ptr=[0 3 7 9 12 16] \& rowids=[ 0 1 3 1 2 4 5 2 3 2 3 4 0 3 4 5 1 4 5] \& nzvals=[10 3 3 9 7 8 4 8 8 7 7 9 \-2 5 9 2 3 13 \-1] .Ve .SS "Sparse Matrix Example" .IX Subsection "Sparse Matrix Example" .Vb 3 \& ##\-\- create a random sparse matrix \& $a = random(100,100); \& $a *= ($a>.9); \& \& ##\-\- encode it \& ($ptr,$rowids,$nzvals) = ccsencode($a); \& \& ##\-\- what did we save? \& sub pdlsize { return PDL::howbig($_[0]\->type)*$_[0]\->nelem; } \& print "Encoding saves us ", \& ($saved = pdlsize($a) \- pdlsize($ptr) \- pdlsize($rowids) \- pdlsize($nzvals)), \& " bytes (", (100.0*$saved/pdlsize($a)), "%)\en"; .Ve .PP \&... prints something like: .PP .Vb 1 \& Encoding saves us 71416 bytes (89.27%) .Ve .SS "Decoding Example" .IX Subsection "Decoding Example" .Vb 2 \& ##\-\- random matrix \& $a = random(100,100); \& \& ##\-\- make an expensive copy of $a by encoding & decoding \& ($ptr,$rowids,$nzvals) = ccsencode($a); \& $a2 = ccsdecode($ptr,$rowids,$nzvals); \& \& ##\-\- ...and make sure it\*(Aqs good \& print all($a==$a2) ? "Decoding is good!\en" : "Nasty icky bug!\en"; .Ve .SH ACKNOWLEDGEMENTS .IX Header "ACKNOWLEDGEMENTS" Perl by Larry Wall. .PP PDL by Karl Glazebrook, Tuomas J. Lukka, Christian Soeller, and others. .PP Original inspiration and algorithms from the SVDLIBC C library by Douglas Rohde; which is itself based on SVDPACKC by Michael Berry, Theresa Do, Gavin O'Brien, Vijay Krishna and Sowmini Varadhan. .SH "KNOWN BUGS" .IX Header "KNOWN BUGS" Many. .SH AUTHOR .IX Header "AUTHOR" Bryan Jurish .SS "Copyright Policy" .IX Subsection "Copyright Policy" Copyright (C) 2005\-2024, Bryan Jurish. All rights reserved. .PP This package is free software, and entirely without warranty. You may redistribute it and/or modify it under the same terms as Perl itself. .SH "SEE ALSO" .IX Header "SEE ALSO" \&\fBperl\fR\|(1), \&\fBPDL\fR\|(3perl), \&\fBPDL::SVDLIBC\fR\|(3perl), \&\fBPDL::CCS::Nd\fR\|(3perl), .PP SVDLIBC: http://tedlab.mit.edu/~dr/SVDLIBC/ .PP SVDPACKC: http://www.netlib.org/svdpack/