File Coverage

blib/lib/Math/MatrixSparse.pm
Criterion Covered Total %
statement 648 1595 40.6
branch 203 782 25.9
condition 40 191 20.9
subroutine 63 136 46.3
pod 0 106 0.0
total 954 2810 33.9


line stmt bran cond sub pod time code
1             package Math::MatrixSparse;
2              
3 6     6   80665 use 5.006;
  6         26  
  6         456  
4 6     6   40 use strict;
  6         15  
  6         234  
5 6     6   36 use warnings;
  6         17  
  6         394  
6              
7 6     6   32 use Carp;
  6         13  
  6         1959  
8              
9             require Exporter;
10              
11             our @ISA = qw(Exporter);
12              
13             # Items to export into callers namespace by default. Note: do not export
14             # names by default without a very good reason. Use EXPORT_OK instead.
15             # Do not simply export all your public functions/methods/constants.
16              
17             # This allows declaration use Math::MatrixSparse ':all';
18             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
19             # will save memory.
20             our %EXPORT_TAGS = ( 'all' => [ qw(
21             splitkey
22             makekey
23             ) ] );
24              
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} });
26              
27             our @EXPORT = qw(
28            
29             );
30             our $VERSION = '0.01';
31              
32             use overload
33 6         89 "+" => "add",
34             "*" => "quickmultiply",
35             "x" => "kronecker",
36             "-" => "subtract",
37             "neg" => "negate",
38             "~" => "transpose",
39             "**" => "exponentiate",
40             "==" => "equals",
41             '""' => "print",
42             "&" => "matrixand",
43             "|" => "matrixor",
44 6     6   15949 "fallback" => undef;
  6         9723  
45              
46              
47             # Preloaded methods go here.
48              
49             ### CREATION METHODS
50             sub new {
51 326     326 0 1104 my ($proto,$name) = @_;
52 326         365 my $this;
53 326         4996 $this->{data} = undef;
54 326         797 $this->{name} = $name;
55 326         615 $this->{rows} = 0;
56 326         562 $this->{columns} =0;
57 326         2116 $this->{special}->{bandwidth} = 0;
58 326         633 $this->{special}->{structure} = "";
59 326         665 $this->{special}->{shape} = "";
60 326         660 $this->{special}->{sign} = "";
61 326         547 $this->{special}->{pattern} = 0;
62 326         618 $this->{special}->{field} = "real";
63              
64 326         518 bless ($this);
65 326         679 return $this;
66             }
67              
68              
69             sub newfromstring {
70 6     6 0 1145 my ($proto, $string,$name) = @_;
71 6         27 my $this=new Math::MatrixSparse;
72 6         51 my @entries = split(/\n/,$string);
73 6         106 $this->{special}->{structure} = ".";
74 6         14 foreach my $entry (@entries) {
75 45         255 my ($i,$j,$value) = $entry=~m/^\s*(\d+)\s+(\d+)\s+(.+)\s*$/;
76 45         123 $this->assign($i,$j,$value);
77             }
78 6         11 $this->{name} =$name;
79 6         14 $this->{special}->{field} = "real";
80 6         25 return $this;
81             }
82              
83             sub newdiag {
84 0     0 0 0 my ($proto, $diag, $name) = @_;
85 0         0 my @data = @{$diag};
  0         0  
86 0         0 my $this = new Math::MatrixSparse;
87 0         0 $this->name($name);
88 0         0 my $i=1;
89 0         0 foreach my $entry (@data){
90 0         0 $this->assign($i,$i,$entry);
91 0         0 $i++;
92             }
93 0         0 $this->{special}->{structure} = "symmetric";
94 0         0 $this->{special}->{shape} = "diagonal";
95 0         0 $this->{special}->{field} = "real";
96 0         0 $this->{special}->{square} = 1;
97 0         0 return $this;
98             }
99              
100              
101              
102             sub newdiagfromstring {
103 0     0 0 0 my ($proto, $string, $name) = @_;
104 0         0 my $this=new Math::MatrixSparse;
105 0         0 my @entries = split(/\n/,$string);
106 0         0 foreach my $entry (@entries) {
107 0         0 my ($i,$value) = $entry=~m/^(\d+)\s+(.+)$/;
108 0         0 $this->assign($i,$i,$value);
109             }
110 0         0 $this->{name} =$name;
111 0         0 $this->{special}->{structure} = "symmetric";
112 0         0 $this->{special}->{field} = "real";
113 0         0 $this->{special}->{shape} = "diagonal";
114 0         0 $this->{special}->{square} = 1;
115 0         0 return $this;
116             }
117              
118              
119             sub newidentity {
120 4     4 0 926 my ($proto, $n,$m) = @_;
121 4         22 my $this = new Math::MatrixSparse;
122 4 100       17 $m=$n unless ($m);
123 4         15 $this->{name} = "I$n";
124 4 50       15 my $min = $m<$n ? $m :$n;
125 4         14 for my $i (1..$min) {
126 340         678 $this->assign($i,$i,1);
127             }
128 4         11 $this->{rows} = $n;
129 4         11 $this->{columns} = $m;
130 4         12 $this->{special}->{structure} = "symmetric";
131 4         10 $this->{special}->{field} = "real";
132 4         12 $this->{special}->{shape} = "diagonal";
133 4         9 $this->{special}->{sign} = "nonnegative";
134 4 100       17 $this->{special}->{square} = 1 if $m==$n;
135 4         7 $this->{special}->{pattern} = 0;
136 4         16 return $this;
137             }
138              
139             sub newrandom {
140 4     4 0 956 my ($proto,$maxrow,$maxcol,$max,$density,$name) = @_;
141 4 50       57 $maxcol = $maxrow unless defined $maxcol;
142 4 50 66     49 $density = 1 unless (defined $density)&&($density>=0) && ($density<=1);
      66        
143 4         20 my $stoch = new Math::MatrixSparse($name);
144 4 100       37 unless (defined $max) {
145 1         3 for my $i (1..$maxrow) {
146 32         53 for my $j (1..$maxcol) {
147 32         154 my $uniform = rand;
148 32 50       74 next unless $uniform<=$density;
149 32         84 $stoch->assign($i,$j,rand);
150             }
151             }
152 1         5 return $stoch;
153             }
154 3 50       11 $name = "" unless defined $name;
155 3         10 $stoch->assign($maxrow,$maxcol,0);
156 3         5 my $k = 1;
157 3         9 while ($k<=$max) {
158 1106         2835 my ($i,$j) = (int($maxrow* rand)+1,int($maxcol* rand)+1);
159 1106 100       4370 next if $stoch->element($i,$j);
160 1100         1417 my $uniform = rand;
161 1100 50       2193 if ($uniform>$density) {
162 0         0 carp "Math::MatrixReal::newrandom ignoring element";
163             }
164 1100 50       4422 next unless $uniform <= $density;
165 1100         2286 $stoch->assign($i,$j,rand);
166 1100         3052 $k++;
167             }
168 3         8 $stoch->{special}->{sign}="nonnegative";
169 3         22 return $stoch;
170             }
171              
172             ### INPUT-OUTPUT METHODS
173             sub newharwellboeing {
174 1     1 0 310 my ($proto, $filename) = @_;
175 1 50       66 open(HB,"<$filename" ) || croak "Math::MatrixSparse::newharwellboeing Can't open $filename\n";
176 1         5 my $this = new Math::MatrixSparse;
177 1         25 $_ = ;
178 1         3 chomp();
179 1         8 my ($ident, $key) = $_ =~ m/^(.*)(........)\s*$/;
180 1         4 $key =~ s/\s*$//;
181 1         3 $this->{name} = $key;
182 1         7 $_ = ;
183 1         2 chomp();
184 1         8 my ($lines, $pline, $rline, $nvline, $rhsline) = $_ =~
185             m/^\s*(\d{1,14})\s*(\d{1,14})\s*(\d{1,14})\s*(\d{1,14})\s*(\d{1,14})\s*$/;
186 1         8 $_ = ;
187 1         2 chomp();
188 1         8 my ($mattype, $rows, $columns, $rvi, $eme) = $_ =~
189             m/^\s*(...)\s*(\d{1,14})\s*(\d{1,14})\s*(\d{1,14})\s*(\d{1,14})\s*$/;
190 1 50       5 if ($mattype =~ /^C/) {
191 0         0 croak "Math::MatrixSparse::newharwellboeing Complex types not implemented, exiting\n";
192             } else {
193 1         3 $this->{special}->{field} = "real";
194             }
195 1         4 $this->{rows} = $rows;
196 1         2 $this->{columns} = $columns;
197 1         25 my $formatline = ;
198 1         2 chomp($formatline);
199 1         12 my ($colft, $rowft, $valft,$rhsft) = $formatline =~ m/(\([a-zA-Z0-9.+-+]+\))/g;
200 1 50 33     11 unless (($colft =~ /I/i)&&($rowft =~ /I/i)) {
201 0         0 carp "Math::MatrixSparse::newharwellboeing non-integer format for rows and columns";
202             }
203 1         3 $valft =~ s/\d+P//g;
204 1         6 my ($valrep, $valsize) = $valft =~ m/(\d+)[a-zA-Z](\d+)/;
205 1         5 my $valregex = '.' x $valsize;
206 1         1 my $rhsspec;
207 1 50       4 if ($rhsline) {
208 0         0 $rhsspec = ;
209 0         0 chomp($rhsspec);
210             }
211             #now read the column pointer data...
212 1         2 my @colpointers;
213 1         5 for my $i (1..$pline) {
214 27         104 $_ = ;
215 27         78 s/^\s*//;
216 27         301 s/\s*$//;
217 27         180 push @colpointers, split(/\s+/);
218             }
219             #...and the row data...
220 1         2 my @rowindex;
221 1         5 for my $i (1..$rline) {
222 259         1005 $_ = ;
223 259         598 s/^\s*//;
224 259         2771 s/\s*$//;
225 259         1831 push @rowindex, split(/\s+/);
226             }
227             #...and any value data. If the matrix is a pattern type, fill
228             # @values with ones.
229 1         2 my @values;
230 1 50       6 if ($mattype =~ m/^P..$/i) {
231 0         0 @values = (1) x $rvi;
232             } else {
233 1         8 for my $i (1..$nvline) {
234 1035         2724 $_ = ;
235 1035         1425 s/D/e/g;
236 1035         6386 push @values, map {s/\s//g; $_+0} m/$valregex/g;
  4140         13894  
  4140         10887  
237             }
238             }
239              
240 1         6 my $curcol = 1;
241 1         5 foreach my $i (0..($#colpointers-1)) {
242 420         657 my $thiscol = $colpointers[$i];
243 420         634 my $nextcol = $colpointers[$i+1];
244 420         2478 my @rowslice = @rowindex[$thiscol-1..$nextcol-2];
245 420         1816 my @valslice = @values[$thiscol-1..$nextcol-2];
246 420         1025 foreach my $j (0..$#rowslice) {
247 4140         10165 $this->assign($rowslice[$j],$curcol,$valslice[$j]);
248             }
249 420         1630 $curcol++;
250             }
251 1 50       8 if ($mattype =~ /^.S.$/) {
    0          
252 1         6 $this->_symmetrify();
253             } elsif ($mattype =~ /^.Z.$/) {
254 0         0 $this->_skewsymmetrify();
255             }
256              
257 1         524 return $this;
258             }
259              
260             sub newmatrixmarket {
261 2     2 0 832 my ($proto, $filename) = @_;
262 2         12 my $this = new Math::MatrixSparse;
263 2 50       9 confess "Math::MatrixSparse::newmatrixmarket undefined filename" unless defined $filename;
264 2 50       101 open(MM,"<$filename" ) || croak "Math::MatrixSparse::newmatrixmarket Can't open $filename for reading";
265 2         69 $_=;
266 2 50       19 unless (/^\%\%MatrixMarket/) {
267 0         0 confess "Math::MatrixSparse::newmatrixmarket Invalid start of file";
268             }
269 2 50       14 unless (/coordinate/i) {
270 0         0 confess "Math::MatrixSparse::newmatrixmarket dense format not implemented";
271             }
272 2 50       10 if (/complex/i) {
273 0         0 carp "Math::MatrixSparse::newmatrixmarket Complex matrices not implemented, ignoring imaginary part\n";
274 0         0 $this->{special}->{field} = "real";
275             } else {
276 2         162 $this->{special}->{field} = "real";
277             }
278 2         6 my $specifications = $_;
279 2         2 my $ispattern;
280             my $issymmetric;
281 0         0 my $isskewsymmetric;
282 2 50       11 if ($specifications =~ /pattern/i) {
283 2         11 $ispattern = 1;
284 2         5 $this->{special}->{pattern} = 1;
285             }
286 2         6 $this->{name} = $filename;
287 2         4 my $startdata=0;
288 2         3 my $entries =0;
289 2         3 my ($rows,$columns);
290              
291 2         16 while () {
292 254 50       519 next if /^\%/;
293 254 100       369 unless ($startdata) {
294 2         45 s/^\s*//;
295 2         15 s/\s*$//;
296 2         11 ($rows,$columns,$entries) = split(/\s+/);
297 2         8 $this->{rows} = $rows;
298 2         4 $this->{columns} = $columns;
299 2         7 $startdata = 1;
300             } else {
301 252         983 s/^\s*//;
302 252         1200 s/\s*$//;
303 252         738 ($rows,$columns,$entries) = split(/\s+/);
304 252 50       552 $entries = 1 if $ispattern;
305 252         506 $this->assign($rows,$columns,$entries);
306             }
307             }
308 2 50       27 if ($specifications =~ /\Wsymmetric/i) {
309 0         0 $this->{special}->{structure} = "symmetric";
310 0         0 return $this->symmetrify();
311             }
312 2 50       19 if ($specifications =~ /skewsymmetric/i) {
313 0         0 $this->{special}->{structure} = "skewsymmetric";
314 0         0 return $this->skewsymmetrify();
315             }
316             # if ($specifications =~ /hermetian/i) {
317             # $this->{special}->{structure} = "hermetian";
318             # return $this->symmetrify();
319             # }
320 2         16 return $this;
321             }
322              
323             sub writematrixmarket {
324 0     0 0 0 my ($matrix, $filename) = @_;
325 0 0       0 open(MM,">$filename" ) || croak "Math::MatrixSparse::newmatrixmarket Can't open $filename for writing";
326 0         0 print MM '%%MatrixMarket matrix coordinate ';
327 0 0       0 print MM $matrix->{special}->{pattern} ? "pattern " : "real ";
328 0 0       0 if ($matrix->{special}->{structure} =~ m/^symmetric/i) {
    0          
329 0         0 $matrix=$matrix->symmetrify();
330 0         0 print MM "symmetric";
331             } elsif ($matrix->{special}->{structure} =~ m/skewsymmetric/i) {
332 0         0 $matrix=$matrix->skewsymmetrify();
333 0         0 print MM "skewsymmetric";
334             } else {
335 0         0 print MM "general\n";
336             }
337 0         0 print MM "$matrix->{rows} $matrix->{columns} ";
338 0         0 print MM $matrix->count() , "\n";
339 0 0       0 if ($matrix->{special}->{pattern}) {
340 0         0 foreach my $key ($matrix->sortbycolumn()) {
341 0         0 my ($i,$j) = &splitkey($key);
342 0 0       0 next unless $matrix->element($i,$j);
343 0         0 print MM "$i $j\n";
344             }
345             } else {
346 0         0 foreach my $key ($matrix->sortbycolumn()) {
347 0         0 my ($i,$j) = &splitkey($key);
348 0         0 print MM "$i $j ", $matrix->{data}{$key}, "\n";
349             }
350             }
351 0         0 return;
352             }
353              
354              
355             sub copy {
356 155     155 0 279 my ($proto,$name) = @_;
357 155         553 my $this = new Math::MatrixSparse;
358 155 50       509 return $this unless defined $proto;
359 155 100       500 if (defined $proto->{data}) {
360 154         179 %{$this->{data}} = %{$proto->{data}};
  154         5958  
  154         2175  
361             }
362 155         715 %{$this->{special}} = %{$proto->{special}};
  155         1244  
  155         507  
363 155 50       694 $this->{name} = defined $name ? $name : $proto->{name};
364 155         807 $this->{rows} = $proto->{rows};
365 155         305 $this->{columns} = $proto->{columns};
366 155         308 return $this;
367             }
368              
369             sub name {
370 0     0 0 0 my ($object,$name) = @_;
371 0         0 $object->{name} = $name;
372 0         0 return $name;
373             }
374              
375             ### INSERTION AND LOOKUP METHODS
376             sub assign {
377 16002     16002 0 27611 my ($object, $i,$j,$x)=@_;
378 16002 50 33     109138 return undef unless ((defined $i) && (defined $j)&&(defined $object));
      33        
379 16002 100       38089 $x = 1 if $object->{special}->{pattern};
380 16002 50       28012 return undef unless defined $x;
381             #update matrix's shape if necessary.
382 16002 50 33     196536 if ((defined $object->{special}) &&
    50 33        
    100 33        
    50 66        
    100 33        
      66        
383             ($object->{special}->{shape} =~ m/diagonal/i) &&
384             ($i!= $j) ) {
385 0 0       0 if ($i<$j) {
386 0         0 $object->{special}->{shape} = "upper"
387             } else {
388 0         0 $object->{special}->{shape} = "lower"
389             }
390             } elsif (($object->{special}->{shape} =~ m/strictlower/i)
391             && ($i<= $j) ) {
392 0 0       0 if ($i==$j) {
393 0         0 $object->{special}->{shape} = "lower";
394             } else {
395 0         0 $object->{special}->{shape}="";
396             }
397             } elsif (($object->{special}->{shape} =~ m/strictupper/i)
398             && ($i>= $j) ) {
399 1 50       4 if ($i==$j) {
400 0         0 $object->{special}->{shape} = "upper";
401             } else {
402 1         3 $object->{special}->{shape}="";
403             }
404             } elsif (($object->{special}->{shape} =~ m/^lower/i) && ($i< $j) ) {
405 0         0 $object->{special}->{shape}="";
406             } elsif (($object->{special}->{shape} =~ m/^upper/i) && ($i= $j) ) {
407 1         4 $object->{special}->{shape}="";
408             }
409 16002 100       40301 $object->{special}->{pattern} = 0 unless ($x==1);
410             #update bandwidth
411 16002 100       41850 if (abs($i-$j) > $object->{special}->{bandwidth}) {
412 216         476 $object->{special}->{bandwidth} = abs($i-$j);
413             }
414             #update symmetric and skew-symmetric structure if necessary
415 16002 50 66     133679 if (
    50 66        
      66        
      33        
416             ($i!=$j) && ( defined $object->{special}->{structure}) &&
417             (
418             ($object->{special}->{structure} =~ /symmetric/i)
419             # ||($object->{special}->{structure} =~ /hermetian/i)
420             )
421             ) {
422 0         0 $object->{special}->{structure} = "";
423             } elsif (($i==$j)&&
424             ($object->{special}->{structure} =~ /skewsymmetric/i)&&
425             ($x)) {
426             #skew-symmetric matrices must have zero diagonal
427 0         0 $object->{special}->{structure} = "";
428             }
429             #update sign if necessary
430 16002 50 33     149095 if ( ($object->{special}->{sign} =~ /^positive/) && ($x<=0) ) {
    50 33        
    100 100        
    50 33        
431 0 0       0 if ($x<0) {
432 0         0 $object->{special}->{sign}="";
433             } else {
434 0         0 $object->{special}->{sign} = "nonnegative";
435             }
436             } elsif ( ($object->{special}->{sign} =~ /^negative/) && ($x>=0) ) {
437 0 0       0 if ($x>0) {
438 0         0 $object->{special}->{sign}="";
439             } else {
440 0         0 $object->{special}->{sign} = "nonpositive";
441             }
442             } elsif ( ($object->{special}->{sign} =~ /nonnegative/i) && ($x<0) ) {
443 2         7 $object->{special}->{sign}="";
444             } elsif ( ($object->{special}->{sign} =~ /nonpositive/i) && ($x>0) ) {
445 0         0 $object->{special}->{sign}="";
446             }
447 16002         29137 my $key = &makekey($i,$j);
448 16002         26168 delete $object->{sortedrows};
449 16002         19817 delete $object->{sortedcolumns};
450 16002         25747 delete $object->{data}->{$key};
451 16002         40229 $object->{data}->{$key} = $x;
452             #update size of matrix, and squareness, if necessary
453 16002 100       34336 $object->{rows} = $i if ($i>$object->{rows});
454 16002 100       32725 $object->{columns} = $j if ($j>$object->{columns});
455 16002         36137 $object->{special}->{square} = ( $object->{columns}==$object->{rows});
456 16002         29864 return $x;
457             }
458              
459             sub assignspecial {
460             #as assign, except that it respects special properties of
461             #the matrix. For example, symmetrical matrices are kept symmetric.
462 0     0 0 0 my ($object, $i,$j,$x)=@_;
463 0 0 0     0 return undef unless ((defined $i) && (defined $j)&&(defined $object));
      0        
464 0 0       0 $x = 1 if $object->{special}->{pattern};
465 0 0       0 return undef unless defined $x;
466 0         0 my $key = &makekey($i,$j);
467 0         0 my $revkey = &makekey($j,$i);
468 0 0       0 if ($object->{special}->{structure} =~ m/^symmetric/i) {
    0          
469 0 0       0 if ($i==$j) {
470 0         0 $object->{data}{$key} = $x;
471             } else {
472 0         0 $object->{data}{$key} = $x;
473 0         0 $object->{data}{$revkey} = $x;
474             }
475             } elsif ($object->{special}->{structure} =~ m/^symmetric/i) {
476 0 0 0     0 if (($i==$j)&&($x)) {
477 0         0 croak "Math::MatrixSparse::assignspecial skewsymmetric matrices must have zero diagonal";
478             } else {
479 0         0 $object->{data}{$key} = $x;
480 0         0 $object->{data}{$revkey} = -$x;
481             }
482             } else {
483 0         0 $object->assign($i,$j,$x);
484             }
485 0         0 return $x;
486             }
487              
488             sub assignkey {
489 2008     2008 0 2476 my ($object, $key,$x)=@_;
490 2008         2907 my ($i,$j) = &splitkey($key);
491 2008 50 33     7859 return unless ((defined $i) && (defined $j));
492 2008         3477 $object->assign($i,$j,$x);
493 2008         3587 return $x;
494             }
495              
496             sub element {
497 55926     55926 0 79431 my ($object, $i,$j) = @_;
498 55926         86378 my $key = &makekey($i,$j);
499 55926 100       158239 if (defined $object->{data}{$key}) {
500 53538         176578 return $object->{data}{$key};
501             } else {
502 2388         6238 return 0;
503             }
504             }
505              
506             sub elementkey {
507 28523     28523 0 34463 my ($object, $key) = @_;
508 28523 100       57685 if (defined $object->{data}{$key}) {
509 28023         63826 return $object->{data}{$key};
510             } else {
511 500         854 return 0;
512             }
513             }
514              
515             sub elements {
516 0     0 0 0 my ($matrix) = @_;
517 0         0 return keys %{$matrix->{data}};
  0         0  
518             }
519              
520              
521             #returns row number $row of matrix as a row matrix
522             sub row {
523 0     0 0 0 my ($matrix, $row,$persist) = @_;
524 0         0 my $matrow = new Math::MatrixSparse;
525 0         0 $matrow->{columns} = $matrix->{columns};
526 0         0 $matrow->{rows} = 1;
527 0         0 $matrow->name($matrix->{name});
528 0 0       0 reuturn $matrow unless $row;
529 0 0       0 if ($persist) {
530 0 0       0 @{$matrix->{sortedrows}} = $matrix->sortbyrow()
  0         0  
531             unless defined $matrix->{sortedrows};
532             }
533 0 0       0 if (defined $matrix->{sortedrows}) {
534             #binary search for proper values
535 0         0 my @rows = @{$matrix->{sortedrows}};
  0         0  
536 0         0 my ($left,$right) = (0,$#rows);
537 0         0 my $mid = int(($right+$left)/2.0);
538 0   0     0 while (
539             ((&splitkey($rows[$mid]))[0] != $row )
540             && ($right-$left>0)
541             ) {
542 0 0       0 if ((&splitkey($rows[$mid]))[0] < $row) {
543 0         0 $left = $mid;
544             } else {
545 0         0 $right = $mid;
546             }
547 0         0 $mid = int(($right+$left)/2.0);
548             }
549 0 0       0 return $matrow unless (&splitkey($rows[$mid]))[0]==$row;
550 0         0 $right = $mid;
551 0   0     0 while ( ($right<=$#rows)&&
552             ((&splitkey($rows[$right]))[0]==$row)
553             )
554             {
555 0         0 $matrow->assign(1,
556             (&splitkey($rows[$right]))[1],
557             $matrix->elementkey($rows[$right]));
558 0         0 $right++;
559             }
560 0         0 $left = $mid-1;
561 0   0     0 while ( ($left) &&
562             ((&splitkey($rows[$left]))[0]==$row)
563             ){
564 0         0 $matrow->assign(1,
565             (&splitkey($rows[$left]))[1],
566             $matrix->elementkey($rows[$left]));
567 0         0 $left--;
568             }
569 0         0 return $matrow;
570             }
571 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
572 0         0 my ($i,$j) = &splitkey($key);
573 0 0       0 $matrow->assignkey($key, $matrix->{data}{$key}) if ($i==$row);
574             }
575 0         0 return $matrow;
576             }
577              
578             #returns column number $col of matrix as a column matrix
579             sub column {
580 0     0 0 0 my ($matrix, $col,$persist) = @_;
581 0         0 my $matcol = new Math::MatrixSparse;
582 0         0 $matcol->{columns} = $matrix->{columns};
583 0         0 $matcol->{cols} = 1;
584 0         0 $matcol->name($matrix->{name});
585 0 0       0 if ($persist) {
586 0 0       0 @{$matrix->{sortedcols}} = $matrix->sortbycolumn() unless defined $matrix->{sortedcols};
  0         0  
587             }
588 0 0       0 if (defined $matrix->{sortedcols}) {
589             #binary search for proper values
590 0         0 my @cols = @{$matrix->{sortedcols}};
  0         0  
591 0         0 my ($left,$right) = (0,$#cols);
592 0         0 my $mid = int(($right+$left)/2.0);
593 0   0     0 while (
594             ((&splitkey($cols[$mid]))[1] != $col )
595             && ($right-$left>0)
596             ) {
597 0 0       0 if ((&splitkey($cols[$mid]))[1] < $col) {
598 0         0 $left = $mid;
599             } else {
600 0         0 $right = $mid;
601             }
602 0         0 $mid = int(($right+$left)/2.0);
603             }
604 0 0       0 return $matcol unless (&splitkey($cols[$mid]))[1]==$col;
605 0         0 $right = $mid;
606 0   0     0 while ( ($right<=$#cols)&&
607             ((&splitkey($cols[$right]))[1]==$col)
608             )
609             {
610 0         0 $matcol->assign((&splitkey($cols[$right]))[0],1,
611             $matrix->elementkey($cols[$right]));
612 0         0 $right++;
613             }
614 0         0 $left = $mid-1;
615 0   0     0 while ( ($left) &&
616             ((&splitkey($cols[$left]))[1]==$col)
617             ){
618 0         0 $matcol->assign((&splitkey($cols[$left]))[0],1,
619             $matrix->elementkey($cols[$left]));
620 0         0 $left--;
621             }
622 0         0 return $matcol;
623             }
624 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
625 0         0 my ($i,$j) = &splitkey($key);
626 0 0       0 $matcol->assignkey($key, $matrix->{data}{$key}) if ($j==$col);
627             }
628 0         0 return $matcol;
629             }
630              
631              
632             ### PRINT
633              
634             sub print {
635 0     0 0 0 my ($object,$name,$rc) = @_;
636 0 0       0 return unless defined $object;
637 0 0       0 my $label = $name ? $name : $object->{name};
638 0         0 my @order ;
639 0 0       0 $rc = "n" unless defined($rc);
640 0 0       0 if ($rc =~ /^r/i) {
    0          
641 0         0 @order = $object->sortbyrow();
642             } elsif ($rc =~ /^c/i) {
643 0         0 @order = $object->sortbycolumn();
644            
645             } else {
646 0         0 @order = keys %{$object->{data}};
  0         0  
647             }
648 0         0 foreach my $key (@order){
649 0         0 my ($i,$j) = &splitkey($key);
650 0         0 print "$label($i, $j) = ", $object->{data}{$key},"\n";
651             }
652 0         0 return "";
653             }
654              
655              
656             ###ARITHMETIC METHODS
657              
658             #left+$right, dimensions must be identical
659             sub add {
660 7     7 0 27 my ($left,$right,$switch) = @_;
661 7 50 33     77 if (($left->{rows} == $right->{rows})&&($left->{columns} == $right->{columns})) {
662            
663 7         34 my $sum= $left->addfree($right);
664              
665 7         20 $sum->{rows} = $left->{rows};
666 7         17 $sum->{columns} = $left->{columns};
667 7         41 return $sum;
668             } else {
669 0         0 return undef;
670             }
671             }
672              
673             #as add, but no restrictions on dimensions.
674             sub addfree {
675 7     7 0 18 my ($left, $right,$switch) = @_;
676 7         37 my $sum = new Math::MatrixSparse;
677 7         112 $sum = $left->copy();
678 7 50 33     86 if ((defined $left->{name})&&(defined $right->{name})) {
679 0         0 $sum->{name} = $left->{name} . "+" . $right->{name};
680             }
681 7         11 foreach my $rightkey (keys %{$right->{data}}) {
  7         317  
682 2557 100       4578 if (defined $sum->{data}{$rightkey}) {
683 1008         1773 $sum->{data}{$rightkey}+= $right->{data}{$rightkey};
684             } else {
685 1549         3915 $sum->{data}{$rightkey} = $right->{data}{$rightkey};
686             }
687             }
688 7 50       222 $sum->{rows} = $left->{rows} >$right->{rows} ?
689             $left->{rows} : $right->{rows};
690 7 50       29 $sum->{columns} = $left->{columns} >$right->{columns} ?
691             $left->{columns} : $right->{columns};
692 7 100       39 if ($left->{special}->{structure} eq $right->{special}->{structure}) {
693 6         21 $sum->{special}->{structure} = $left->{special}->{structure};
694             }
695 7 100       31 if ($left->{special}->{shape} eq $right->{special}->{shape}) {
696 3         148 $sum->{special}->{shape} = $left->{special}->{shape};
697             }
698 7         30 return $sum;
699             }
700              
701              
702             sub subtract {
703 73     73 0 169 my ($left, $right, $switch) = @_;
704 73 50       214 if ($switch) {
705 0         0 ($left,$right) = ($right, $left);
706             }
707 73 50 33     485 if (
708             ($left->{rows} == $right->{rows})&&
709             ($left->{columns} == $right->{columns})
710             ) {
711 73         273 my $diff= $left->subtractfree($right);
712 73         154 $diff->{rows} = $left->{rows};
713 73         120 $diff->{columns} = $left->{columns};
714 73         362 return $diff;
715             } else {
716 0         0 return undef;
717             }
718            
719             }
720              
721             #as subtract, but no restrictions on dimensions.
722             sub subtractfree {
723 73     73 0 115 my ($left, $right,$switch) = @_;
724 73         392 my $diff = new Math::MatrixSparse;
725 73         291 $diff = $left->copy();
726 73         312 foreach my $rightkey (keys %{$right->{data}}) {
  73         534  
727 2528 100       4014 if (defined $diff->{data}{$rightkey}) {
728 1956         3487 $diff->{data}{$rightkey}-= $right->{data}{$rightkey};
729             } else {
730 572         1063 $diff->{data}{$rightkey} = -1* $right->{data}{$rightkey};
731             }
732             }
733 73 50 33     422 if ((defined $left->{name})&&(defined $right->{name})) {
734 0         0 $diff->{name} = $left->{name} . "-" . $right->{name};
735             }
736 73 50       252 $diff->{rows} = $left->{rows} >$right->{rows} ?
737             $left->{rows} : $right->{rows};
738 73 50       288 $diff->{columns} = $left->{columns} >$right->{columns} ?
739             $left->{columns} : $right->{columns};
740 73 50       279 if ($left->{special}->{structure} eq $right->{special}->{structure}) {
741 73         224 $diff->{special}->{structure} = $left->{special}->{structure};
742             }
743 73 50       196 if ($left->{special}->{shape} eq $right->{special}->{shape}) {
744 73         157 $diff->{special}->{shape} = $left->{special}->{shape};
745             }
746 73         154 return $diff;
747             }
748              
749             sub negate {
750 0     0 0 0 my ($matrix) = @_;
751 0         0 return $matrix->multiplyscalar(-1);
752             }
753              
754             sub _negate {
755 0     0   0 my ($matrix) = @_;
756 0         0 return $matrix->_multiplyscalar(-1);
757             }
758              
759             sub multiplyscalar {
760 3     3 0 6 my ($matrix, $scalar) = @_;
761 3         12 my $product = $matrix->copy();
762 3 50       13 $scalar = 0 unless $scalar;
763 3         12 foreach my $key (keys %{$product->{data}}) {
  3         194  
764 2008         3774 $product->assignkey($key,$matrix->elementkey($key)*$scalar);
765             }
766 3 50       295 $product->{name} = "$scalar*".$product->{name} if defined $product->{name};
767 3 50       13 if ($scalar <0 ) {
768 0 0       0 if ($matrix->{special}->{sign} =~ m/positive/i) {
    0          
    0          
    0          
769 0         0 $product->{special}->{sign} = "negative";
770             } elsif ($matrix->{special}->{sign} =~ m/negative/i) {
771 0         0 $product->{special}->{sign} = "positive";
772             } elsif ($matrix->{special}->{sign} =~ m/nonpositive/i) {
773 0         0 $product->{special}->{sign} = "nonnegative";
774             } elsif ($matrix->{special}->{sign} =~ m/nonnegative/i) {
775 0         0 $product->{special}->{sign} = "nonpositive";
776             }
777             } else {
778 3         10 $product->{special}->{sign} =$matrix->{special}->{sign};
779             }
780 3 50       10 $product->{special}->{sign} = "zero" unless $scalar;
781 3         16 return $product;
782             }
783              
784             sub _multiplyscalar {
785 0     0   0 my ($matrix, $scalar) = @_;
786 0 0       0 $scalar = 0 unless $scalar;
787 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
788 0         0 $matrix->{data}{$key} = $matrix->{data}{$key}*$scalar;
789             }
790 0 0       0 if ($scalar <0 ) {
791 0 0       0 if ($matrix->{special}->{sign} =~ m/positive/i) {
    0          
    0          
    0          
792 0         0 $matrix->{special}->{sign} = "negative";
793             } elsif ($matrix->{special}->{sign} =~ m/negative/i) {
794 0         0 $matrix->{special}->{sign} = "positive";
795             } elsif ($matrix->{special}->{sign} =~ m/nonpositive/i) {
796 0         0 $matrix->{special}->{sign} = "nonnegative";
797             } elsif ($matrix->{special}->{sign} =~ m/nonnegative/i) {
798 0         0 $matrix->{special}->{sign} = "nonpositive";
799             }
800             }
801 0 0       0 $matrix->{special}->{sign} = "zero" unless $scalar;
802 0         0 return $matrix;
803             }
804              
805              
806             #finds $left*$right, if compatible
807             sub multiply {
808 0     0 0 0 my ($left,$right,$switch) = @_;
809 0 0       0 unless (ref($right)) {
810 0         0 return $left->multiplyscalar($right);
811             }
812 0 0       0 return undef if ($left->{columns} != $right->{rows});
813 0         0 my $product = new Math::MatrixSparse;
814 0         0 $product->{rows} = $left->{rows};
815 0         0 $product->{columns} = $right->{columns};
816 0 0 0     0 if ((defined $left->{name})&&(defined $right->{name})) {
817 0         0 $product->{name} = $left->{name} . "*" . $right->{name};
818             }
819 0         0 foreach my $leftkey (keys %{$left->{data}}) {
  0         0  
820 0         0 my ($li,$lj) = &splitkey($leftkey);
821 0         0 foreach my $rightkey (keys %{$right->{data}}) {
  0         0  
822 0         0 my ($ri,$rj) = &splitkey($rightkey);
823 0 0       0 next unless ($lj==$ri);
824 0         0 my $thiskey = &makekey($li, $rj);
825 0         0 my $prod = $left->{data}{$leftkey}*$right->{data}{$rightkey};
826 0 0       0 if (defined $product->{data}{$thiskey}) {
827 0         0 $product->{data}{$thiskey} += $prod;
828             } else {
829 0         0 $product->{data}{$thiskey} = $prod;
830             }
831              
832             }
833            
834             }
835 0 0 0     0 if (
836             ($left->{special}->{sign} =~ /zero/i)||
837             ($right->{special}->{sign} =~ /zero/i)) {
838 0         0 $product->{special}->{sign} = "zero";
839 0         0 return $product;
840             }
841 0 0       0 if ($left->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
842 0         0 $product->{special}->{sign} = $right->{special}->{sign};
843             } elsif ($left->{special}->{sign} =~ /nonpositive/i) {
844 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
845 0         0 $product->{special}->{sign} = "nonpositive";
846             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
847 0         0 $product->{special}->{sign} = "nonnegative";
848             } elsif ($right->{special}->{sign} =~ /^negative/i) {
849 0         0 $product->{special}->{sign} = "nonnegative";
850             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
851 0         0 $product->{special}->{sign} = "nonpositive";
852             }
853             } elsif ($left->{special}->{sign} =~ /^negative/i) {
854 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
855 0         0 $product->{special}->{sign} = "negative";
856             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
857 0         0 $product->{special}->{sign} = "nonnegative";
858             } elsif ($right->{special}->{sign} =~ /^negative/i) {
859 0         0 $product->{special}->{sign} = "positive";
860             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
861 0         0 $product->{special}->{sign} = "nonpositive";
862             }
863             } elsif ($left->{special}->{sign} =~ /nonnegative/i) {
864 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
865 0         0 $product->{special}->{sign} = "nonnegative";
866             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
867 0         0 $product->{special}->{sign} = "nonpositive";
868             } elsif ($right->{special}->{sign} =~ /^negative/i) {
869 0         0 $product->{special}->{sign} = "nonpositive";
870             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
871 0         0 $product->{special}->{sign} = "nonnegative";
872             }
873             }
874 0         0 return $product;
875            
876             }
877              
878              
879             sub quickmultiply {
880 46     46 0 748 my ($left,$right,$switch) = @_;
881 46 100       162 unless (ref($right)) {
882 3         17 return $left->multiplyscalar($right);
883             }
884 43 50       161 return undef if ($left->{columns} != $right->{rows});
885 43         181 my $product = new Math::MatrixSparse;
886 43         96 $product->{special}->{structure} = "";
887 43         117 $product->{special}->{pattern}=0;
888 43         82 $product->{special}->{shape} = "";
889 43         123 $product->{rows} = $left->{rows};
890 43         102 $product->{columns} = $right->{columns};
891 43         173 my @leftcols = $left->sortbycolumn();
892 43         680 my @rightrows = $right->sortbyrow();
893 43 100 100     633 if ((defined $left->{name})&&(defined $right->{name})) {
894 2         9 $product->{name} = $left->{name} . "*" . $right->{name};
895             }
896 43         73 my $lastrow = 0;
897 43         87 foreach my $leftkey (@leftcols) {
898 10301         16937 my ($li,$lj) = &splitkey($leftkey);
899 10301         12461 my $i = 0;
900 10301         9571 my $thiskey;
901 10301 100       22218 if ($lj >$lastrow ) {
902 2309         2424 $lastrow = $lj;
903             #remove elements that won't be used again in multiplication
904 2309         4663 while (defined ($thiskey = $rightrows[0])){
905 11379         23128 my ($ri,$rj) = &splitkey($thiskey);
906 11379 100       25027 last if $ri>=$lj;
907 9106         19649 shift @rightrows;
908             }
909             }
910 10301         13320 foreach my $rightkey (@rightrows) {
911 223647         353512 my ($ri,$rj) = &splitkey($rightkey);
912 223647 100       517914 last if ($ri>$lj);
913 213530 50       351497 next if ($ri<$lj);
914 213530         334972 my $thiskey = &makekey($li, $rj);
915 213530         517723 my $prod = $left->{data}{$leftkey}*$right->{data}{$rightkey};
916 213530 100       561877 if (defined $product->{data}{$thiskey}) {
917 189508         396356 $product->{data}{$thiskey} += $prod;
918             } else {
919 24022         71393 $product->{data}{$thiskey} = $prod;
920             }
921              
922             }
923            
924             }
925 43 50 33     391 if (
926             ($left->{special}->{sign} =~ /zero/i)||
927             ($right->{special}->{sign} =~ /zero/i)) {
928 0         0 $product->{special}->{sign} = "zero";
929 0         0 return $product;
930             }
931 43 50       536 if ($left->{special}->{sign} =~ /^positive/i) {
    50          
    50          
    100          
932 0         0 $product->{special}->{sign} = $right->{special}->{sign};
933             } elsif ($left->{special}->{sign} =~ /nonpositive/i) {
934 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
935 0         0 $product->{special}->{sign} = "nonpositive";
936             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
937 0         0 $product->{special}->{sign} = "nonnegative";
938             } elsif ($right->{special}->{sign} =~ /^negative/i) {
939 0         0 $product->{special}->{sign} = "nonnegative";
940             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
941 0         0 $product->{special}->{sign} = "nonpositive";
942             }
943             } elsif ($left->{special}->{sign} =~ /^negative/i) {
944 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
945 0         0 $product->{special}->{sign} = "negative";
946             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
947 0         0 $product->{special}->{sign} = "nonnegative";
948             } elsif ($right->{special}->{sign} =~ /^negative/i) {
949 0         0 $product->{special}->{sign} = "positive";
950             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
951 0         0 $product->{special}->{sign} = "nonpositive";
952             }
953             } elsif ($left->{special}->{sign} =~ /nonnegative/i) {
954 15 50       158 if ($right->{special}->{sign} =~ /^positive/i) {
    50          
    50          
    50          
955 0         0 $product->{special}->{sign} = "nonnegative";
956             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
957 0         0 $product->{special}->{sign} = "nonpositive";
958             } elsif ($right->{special}->{sign} =~ /^negative/i) {
959 0         0 $product->{special}->{sign} = "nonpositive";
960             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
961 15         48 $product->{special}->{sign} = "nonnegative";
962             }
963             }
964 43         1959 return $product;
965            
966             }
967              
968             sub quickmultiplyfree {
969 0     0 0 0 my ($left,$right,$switch) = @_;
970 0 0       0 unless (ref($right)) {
971 0         0 return $left->multiplyscalar($right);
972             }
973 0         0 my $product = new Math::MatrixSparse;
974 0         0 $product->{rows} = $left->{rows};
975 0         0 $product->{columns} = $right->{columns};
976 0         0 my @leftcols = $left->sortbycolumn();
977 0         0 my @rightrows = $right->sortbyrow();
978 0 0 0     0 if ((defined $left->{name})&&(defined $right->{name})) {
979 0         0 $product->{name} = $left->{name} . "*" . $right->{name};
980             }
981 0         0 my $lastrow = 0;
982 0         0 foreach my $leftkey (@leftcols) {
983 0         0 my ($li,$lj) = &splitkey($leftkey);
984 0         0 my $i = 0;
985 0         0 my $thiskey;
986 0 0       0 if ($lj >$lastrow ) {
987 0         0 $lastrow = $lj;
988             #remove elements that won't be used again in multiplication
989 0         0 while (defined ($thiskey = $rightrows[0])){
990 0         0 my ($ri,$rj) = &splitkey($thiskey);
991 0 0       0 last if $ri>=$lj;
992 0         0 shift @rightrows;
993             }
994             }
995 0         0 foreach my $rightkey (@rightrows) {
996 0         0 my ($ri,$rj) = &splitkey($rightkey);
997 0 0       0 last if ($ri>$lj);
998 0 0       0 next if ($ri<$lj);
999 0         0 my $thiskey = &makekey($li , $rj);
1000 0         0 my $prod = $left->{data}{$leftkey}*$right->{data}{$rightkey};
1001 0 0       0 if (defined $product->{data}{$thiskey}) {
1002 0         0 $product->{data}{$thiskey} += $prod;
1003             } else {
1004 0         0 $product->{data}{$thiskey} = $prod;
1005             }
1006              
1007             }
1008            
1009             }
1010 0 0 0     0 if (
1011             ($left->{special}->{sign} =~ /zero/i)||
1012             ($right->{special}->{sign} =~ /zero/i)) {
1013 0         0 $product->{special}->{sign} = "zero";
1014 0         0 return $product;
1015             }
1016 0 0       0 if ($left->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1017 0         0 $product->{special}->{sign} = $right->{special}->{sign};
1018             } elsif ($left->{special}->{sign} =~ /nonpositive/i) {
1019 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1020 0         0 $product->{special}->{sign} = "nonpositive";
1021             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1022 0         0 $product->{special}->{sign} = "nonnegative";
1023             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1024 0         0 $product->{special}->{sign} = "nonnegative";
1025             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1026 0         0 $product->{special}->{sign} = "nonpositive";
1027             }
1028             } elsif ($left->{special}->{sign} =~ /^negative/i) {
1029 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1030 0         0 $product->{special}->{sign} = "negative";
1031             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1032 0         0 $product->{special}->{sign} = "nonnegative";
1033             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1034 0         0 $product->{special}->{sign} = "positive";
1035             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1036 0         0 $product->{special}->{sign} = "nonpositive";
1037             }
1038             } elsif ($left->{special}->{sign} =~ /nonnegative/i) {
1039 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1040 0         0 $product->{special}->{sign} = "nonnegative";
1041             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1042 0         0 $product->{special}->{sign} = "nonpositive";
1043             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1044 0         0 $product->{special}->{sign} = "nonpositive";
1045             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1046 0         0 $product->{special}->{sign} = "nonnegative";
1047             }
1048             }
1049 0         0 return $product;
1050            
1051             }
1052              
1053              
1054             #as multiply, but no restrictions on dimensions.
1055             sub multiplyfree {
1056 0     0 0 0 my ($left,$right,$switch) = @_;
1057 0         0 my $product = new Math::MatrixSparse;
1058 0         0 $product->{rows} = $left->{rows};
1059 0         0 $product->{columns} = $right->{columns};
1060 0 0 0     0 if ((defined $left->{name})&&(defined $right->{name})) {
1061 0         0 $product->{name} = $left->{name} . "*" . $right->{name};
1062             }
1063 0         0 foreach my $leftkey (keys %{$left->{data}}) {
  0         0  
1064 0         0 my ($li,$lj) = &splitkey($leftkey);
1065 0         0 foreach my $rightkey (keys %{$right->{data}}) {
  0         0  
1066 0         0 my ($ri,$rj) = &splitkey($rightkey);
1067 0 0       0 next unless ($lj==$ri);
1068 0         0 my $thiskey = &makekey($li, $rj);
1069 0         0 my $prod = $left->{data}{$leftkey}*$right->{data}{$rightkey};
1070 0 0       0 if (defined $product->{data}{$thiskey}) {
1071 0         0 $product->{data}{$thiskey} += $prod;
1072             } else {
1073 0         0 $product->{data}{$thiskey} = $prod;
1074             }
1075              
1076             }
1077             }
1078 0 0 0     0 if (
1079             ($left->{special}->{sign} =~ /zero/i)||
1080             ($right->{special}->{sign} =~ /zero/i)) {
1081 0         0 $product->{special}->{sign} = "zero";
1082 0         0 return $product;
1083             }
1084 0 0       0 if ($left->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1085 0         0 $product->{special}->{sign} = $right->{special}->{sign};
1086             } elsif ($left->{special}->{sign} =~ /nonpositive/i) {
1087 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1088 0         0 $product->{special}->{sign} = "nonpositive";
1089             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1090 0         0 $product->{special}->{sign} = "nonnegative";
1091             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1092 0         0 $product->{special}->{sign} = "nonnegative";
1093             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1094 0         0 $product->{special}->{sign} = "nonpositive";
1095             }
1096             } elsif ($left->{special}->{sign} =~ /^negative/i) {
1097 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1098 0         0 $product->{special}->{sign} = "negative";
1099             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1100 0         0 $product->{special}->{sign} = "nonnegative";
1101             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1102 0         0 $product->{special}->{sign} = "positive";
1103             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1104 0         0 $product->{special}->{sign} = "nonpositive";
1105             }
1106             } elsif ($left->{special}->{sign} =~ /nonnegative/i) {
1107 0 0       0 if ($right->{special}->{sign} =~ /^positive/i) {
    0          
    0          
    0          
1108 0         0 $product->{special}->{sign} = "nonnegative";
1109             } elsif ($right->{special}->{sign} =~ /nonpositive/i) {
1110 0         0 $product->{special}->{sign} = "nonpositive";
1111             } elsif ($right->{special}->{sign} =~ /^negative/i) {
1112 0         0 $product->{special}->{sign} = "nonpositive";
1113             } elsif ($right->{special}->{sign} =~ /nonnegative/i) {
1114 0         0 $product->{special}->{sign} = "nonnegative";
1115             }
1116             }
1117 0         0 return $product;
1118            
1119             }
1120              
1121             sub kronecker {
1122 0     0 0 0 my ($left,$right);
1123 0         0 my ($rr,$rc) = $right->size();
1124 0 0 0     0 return undef unless ($rr&&$rc);
1125 0         0 my $kroprod = new Math::MatrixSparse;
1126 0         0 foreach my $key (keys %{$left->{data}}) {
  0         0  
1127 0         0 my ($i,$j) = &splitkey($key);
1128 0         0 $kroprod->_insert($i*$rr,$j*$rc,$right*$left->elementkey($key));
1129             }
1130 0 0 0     0 if ((defined $left->{name})&&(defined $right->{name})) {
1131 0         0 $kroprod->{name} = $left->{name} . "x" . $right->{name};
1132             }
1133 0         0 return $kroprod;
1134             }
1135              
1136             sub termmult {
1137 0     0 0 0 my ($left,$right) = @_;
1138 0         0 my $termprod = $left->copy();
1139 0         0 foreach my $key (keys %{$termprod->{data}}) {
  0         0  
1140 0         0 $termprod->assignkey($key,$termprod->elementkey($key)* $right->elementkey($key));
1141             }
1142 0         0 return $termprod;
1143            
1144             }
1145              
1146             sub exponentiate {
1147 1     1 0 627 my ($matrix,$power) = @_;
1148 1 50       7 unless ($matrix->is_square()) {
1149 0         0 carp "Math::MatrixSparse::exponentiate matrix must be square";
1150 0         0 return undef ;
1151             }
1152 1 50       5 return Math::MatrixSparse->newidentity($matrix->{rows}) unless $power;
1153 1 50       4 unless ($power>0) {
1154 0         0 carp "Math::MatrixSparse::exponentiate exponent must be positive";
1155 0         0 return undef ;
1156             }
1157 1 50       8 unless ($power =~ /^[+]?\d+/) {
1158 0         0 carp "Math::MatrixSparse::exponentiate exponent must be an integer";
1159 0         0 return undef ;
1160             }
1161 1         6 my $product = $matrix->copy();
1162             # $product->clearspecials();
1163 1         6 for my $i (2..$power) {
1164 2         9 $product = $product->quickmultiply($matrix);
1165             }
1166 1 50       7 $product->{name} = $matrix->{name} ."^$power" if $matrix->{name};
1167 1         7 return $product;
1168             }
1169              
1170             sub largeexponentiate {
1171             #find matrix^power using the square-and-multiply method
1172 2     2 0 712 my ($matrix,$power) = @_;
1173 2 50       13 unless ($matrix->is_square()) {
1174 0         0 carp "Math::MatrixSparse::exponentiate matrix must be square";
1175 0         0 return undef ;
1176             }
1177 2 50       8 return Math::MatrixSparse->newidentity($matrix->{rows}) unless $power;
1178 2 50       6 unless ($power>0) {
1179 0         0 carp "Math::MatrixSparse::exponentiate exponent must be positive";
1180 0         0 return undef ;
1181             }
1182 2 50       12 unless ($power =~ /^[+]?\d+/) {
1183 0         0 carp "Math::MatrixSparse::exponentiate exponent must be an integer";
1184 0         0 return undef ;
1185             }
1186             #get a representation of $exponent in binary
1187 2         20 my $bitstr = unpack('B32',pack('N',$power));
1188 2         11 $bitstr =~s/^0*//;
1189 2         11 my @bitstr=split(//,$bitstr);
1190 2         16 my $z = Math::MatrixSparse->newidentity($matrix->{rows});
1191 2         6 foreach my $bit (@bitstr){
1192 6         35 $z = ($z*$z);
1193 6 100       1211 if ($bit){
1194 3         14 $z = ($z*$matrix);
1195             }
1196             }
1197 2         10 $z->{name} = "";
1198 2 50       12 $z->{name} = $matrix->{name} . "^$power" if $matrix->{name};
1199 2         19 return $z;
1200             }
1201              
1202              
1203             sub transpose {
1204 7     7 0 768 my ($matrix) = @_;
1205 7         42 my $this= new Math::MatrixSparse;
1206 7         20 $this->{rows} = $matrix->{columns};
1207 7         21 $this->{columns} = $matrix->{rows};
1208 7         14 foreach my $key (keys %{$matrix->{data}}) {
  7         432  
1209 2730         4838 my ($i,$j) = &splitkey($key);
1210 2730         7967 $this->assign($j,$i,$matrix->{data}{$key});
1211             }
1212 7 50       427 $this->{name} = $matrix->{name} . "'" if $matrix->{name};
1213 7         27 $this->{special}->{structure} = $matrix->{special}->{structure};
1214 7         26 $this->{special}->{sign} = $matrix->{special}->{sign};
1215 7         18 $this->{special}->{pattern} = $matrix->{special}->{pattern};
1216 7         23 $this->{special}->{square} = $matrix->{special}->{square};
1217 7 50       132 if ($matrix->{special}->{shape} =~ /diagonal/i) {
    50          
    100          
    100          
    100          
1218 0         0 $this->{special}->{shape} = "diagonal";
1219             } elsif ($matrix->{special}->{shape} =~ /^lower/i) {
1220 0         0 $this->{special}->{shape} = "upper";
1221             } elsif ($matrix->{special}->{shape} =~ /^upper/i) {
1222 1         4 $this->{special}->{shape} = "lower";
1223             } elsif ($matrix->{special}->{shape} =~ /strictupper/i) {
1224 1         4 $this->{special}->{shape} = "strictlower";
1225             } elsif ($matrix->{special}->{shape} =~ /strictlower/i) {
1226 1         4 $this->{special}->{shape} = "strictlower";
1227             } else {
1228 4         11 $this->{special}->{shape} = "";
1229             }
1230 7         50 return $this;
1231             }
1232              
1233              
1234             sub terminvert {
1235 3     3 0 4 my ($matrix) = @_;
1236 3         56 my $this = $matrix->copy();
1237 3         5 foreach my $key (keys %{$this->{data}}) {
  3         20  
1238 96 50       190 next unless $this->{data}{$key};
1239 96         156 $this->{data}{$key} = 1.0/($this->{data}{$key});
1240             }
1241 3         13 return $this;
1242             }
1243              
1244             sub _terminvert {
1245 0     0   0 my ($matrix) = @_;
1246 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1247 0 0       0 next unless $matrix->{data}{$key};
1248 0         0 $matrix->{data}{$key} = 1.0/($matrix->{data}{$key});
1249             }
1250 0         0 return $matrix;
1251             }
1252              
1253              
1254              
1255             ### DISSECTION METHODS
1256             sub diagpart {
1257 7     7 0 1295 my ($matrix,$offset) = @_;
1258 7         53 my $diag = new Math::MatrixSparse;
1259 7 50       37 $offset = 0 unless defined $offset;
1260 7         15 foreach my $key (keys %{$matrix->{data}}){
  7         262  
1261 1846         2810 my ($i,$j) = &splitkey($key);
1262 1846 100       4986 next unless ($i == ($j+$offset));
1263 199         499 $diag->assign($i,$j,$matrix->{data}{$key});
1264             }
1265 7         168 $diag->{rows} = $matrix->{rows};
1266 7         21 $diag->{columns} = $matrix->{columns};
1267 7         21 $diag->{special}->{shape} = "diagonal";
1268 7         42 return $diag;
1269             }
1270              
1271             sub nondiagpart {
1272 1     1 0 3 my ($matrix,$offset) = @_;
1273 1         6 my $diag = new Math::MatrixSparse;
1274 1 50       5 $offset = 0 unless defined $offset;
1275 1         3 foreach my $key (keys %{$matrix->{data}}){
  1         56  
1276 501         959 my ($i,$j) = &splitkey($key);
1277 501 100       1210 next if ($i == ($j+$offset));
1278 500         1457 $diag->assign($i,$j,$matrix->{data}{$key});
1279             }
1280 1         87 $diag->{rows} = $matrix->{rows};
1281 1         5 $diag->{columns} = $matrix->{columns};
1282 1         7 return $diag;
1283             }
1284              
1285             sub lowerpart {
1286 6     6 0 707 my ($matrix) = @_;
1287 6         33 my $lower = new Math::MatrixSparse;
1288 6         13 foreach my $key (keys %{$matrix->{data}}){
  6         275  
1289 1746         2880 my ($i,$j) = &splitkey($key);
1290 1746 100       4487 next unless ($i > $j);
1291 825         2634 $lower->assign($i,$j,$matrix->{data}{$key});
1292             }
1293 6         214 $lower->{rows} = $matrix->{rows};
1294 6         19 $lower->{columns} = $matrix->{columns};
1295 6         14 $lower->{special}->{shape} = "strictlower";
1296              
1297 6         29 return $lower;
1298             }
1299              
1300             sub nonlowerpart {
1301 2     2 0 818 my ($matrix) = @_;
1302 2         13 my $lower = new Math::MatrixSparse;
1303 2         6 foreach my $key (keys %{$matrix->{data}}){
  2         135  
1304 1002         1631 my ($i,$j) = &splitkey($key);
1305 1002 100       2374 next if ($i > $j);
1306 452         1225 $lower->assign($i,$j,$matrix->{data}{$key});
1307             }
1308 2         103 $lower->{rows} = $matrix->{rows};
1309 2         6 $lower->{columns} = $matrix->{columns};
1310 2         5 $lower->{special}->{shape} = "upper";
1311 2         15 return $lower;
1312             }
1313              
1314             sub upperpart {
1315 6     6 0 982 my ($matrix) = @_;
1316 6         26 my $upper = new Math::MatrixSparse;
1317 6         13 foreach my $key (keys %{$matrix->{data}}){
  6         213  
1318 1746         2683 my ($i,$j) = &splitkey($key);
1319 1746 100       4105 next unless ($i < $j);
1320 822         2175 $upper->assign($i,$j,$matrix->{data}{$key});
1321             }
1322 6         184 $upper->{rows} = $matrix->{rows};
1323 6         16 $upper->{columns} = $matrix->{columns};
1324 6         45 $upper->{special}->{shape} = "strictupper";
1325 6         27 return $upper;
1326             }
1327              
1328             sub nonupperpart {
1329 1     1 0 379 my ($matrix) = @_;
1330 1         6 my $upper = new Math::MatrixSparse;
1331 1         3 foreach my $key (keys %{$matrix->{data}}){
  1         62  
1332 501         758 my ($i,$j) = &splitkey($key);
1333 501 100       1287 next if ($i < $j);
1334 276         774 $upper->assign($i,$j,$matrix->{data}{$key});
1335             }
1336 1         85 $upper->{rows} = $matrix->{rows};
1337 1         4 $upper->{columns} = $matrix->{columns};
1338 1         5 $upper->{special}->{shape} = "lower";
1339              
1340 1         8 return $upper;
1341             }
1342              
1343              
1344             sub _diagpart {
1345 0     0   0 my ($matrix,$offset) = @_;
1346 0 0       0 $offset = 0 unless defined $offset;
1347 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1348 0         0 my ($i,$j) = &splitkey($key);
1349 0 0       0 $matrix->delete($i,$j) unless ($i == ($j+$offset));
1350             }
1351 0         0 $matrix->{special}->{shape} = "diagonal";
1352 0         0 return $matrix;
1353             }
1354              
1355             sub _nondiagpart {
1356 0     0   0 my ($matrix,$offset) = @_;
1357 0 0       0 $offset = 0 unless defined $offset;
1358 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1359 0         0 my ($i,$j) = &splitkey($key);
1360 0 0       0 $matrix->delete($i,$j) if ($i == ($j+$offset));
1361             }
1362 0 0       0 $matrix->{special}->{shape}="" if
1363             $matrix->{special}->{shape} =~ m/diagonal/i;
1364 0         0 return $matrix;
1365             }
1366              
1367              
1368             sub _lowerpart {
1369 0     0   0 my ($matrix) = @_;
1370 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1371 0         0 my ($i,$j) = &splitkey($key);
1372 0 0       0 $matrix->delete($i,$j) unless ($i > $j);
1373             }
1374 0         0 $matrix->{special}->{shape} = "strictlower";
1375 0         0 return $matrix;
1376             }
1377              
1378             sub _nonlowerpart {
1379 3     3   524 my ($matrix) = @_;
1380 3         7 foreach my $key (keys %{$matrix->{data}}){
  3         1240  
1381 4767         7642 my ($i,$j) = &splitkey($key);
1382 4767 100       15277 $matrix->delete($i,$j) if ($i > $j);
1383             }
1384 3         874 $matrix->{special}->{shape} = "upper";
1385 3         11 return $matrix;
1386             }
1387              
1388             sub _upperpart {
1389 1     1   3 my ($matrix) = @_;
1390 1         1 foreach my $key (keys %{$matrix->{data}}){
  1         56  
1391 501         874 my ($i,$j) = &splitkey($key);
1392 501 100       1629 $matrix->delete($i,$j) unless ($i < $j);
1393             }
1394 1         71 $matrix->{special}->{shape} = "strictupper";
1395 1         4 return $matrix;
1396             }
1397              
1398             sub _nonupperpart {
1399 0     0   0 my ($matrix) = @_;
1400 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1401 0         0 my ($i,$j) = &splitkey($key);
1402 0 0       0 $matrix->delete($i,$j) if ($i < $j);
1403             }
1404 0         0 $matrix->{special}->{shape} = "lower";
1405 0         0 return $matrix;
1406             }
1407              
1408              
1409             sub symmetricpart {
1410             #.5*( A+A' )
1411 1     1 0 575 my ($matrix) = @_;
1412 1         6 my $sp =($matrix+$matrix->transpose());
1413 1         55 $sp = 0.5*$sp;
1414 1         88 $sp->{special}->{structure} = "symmetric";
1415 1         9 return $sp;
1416            
1417             }
1418             sub _symmetricpart {
1419             #.5*( A+A' )
1420 0     0   0 my ($matrix) = @_;
1421 0         0 my $sp =($matrix+$matrix->transpose());
1422 0         0 $sp = 0.5*$sp;
1423 0         0 $sp->{special}->{structure} = "symmetric";
1424 0         0 return $matrix=$sp->copy();
1425             }
1426              
1427             sub skewsymmetricpart {
1428             #.5*( A-A' )
1429 1     1 0 1 my ($matrix) = @_;
1430 1         10 my $ssp= 0.5*($matrix-$matrix->transpose());
1431 1         210 $ssp->{special}->{structure} = "skewsymmetric";
1432 1         8 return $ssp;
1433             }
1434              
1435             sub _skewsymmetricpart {
1436             #.5*( A-A' )
1437 0     0   0 my ($matrix) = @_;
1438 0         0 my $ssp= 0.5*($matrix-$matrix->transpose());
1439 0         0 $ssp->{special}->{structure} = "skewsymmetric";
1440 0         0 return $matrix=$ssp->copy();
1441             }
1442              
1443              
1444             sub positivepart {
1445 0     0 0 0 my ($matrix) = @_;
1446 0         0 my $pos = new Math::MatrixSparse;
1447 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1448 0 0       0 next unless $matrix->elementkey($key) >0;
1449 0         0 $pos->assignkey($key,$matrix->{data}{$key});
1450             }
1451 0         0 $pos->{rows} = $matrix->{rows};
1452 0         0 $pos->{columns} = $matrix->{columns};
1453 0         0 return $pos;
1454             }
1455              
1456              
1457             sub negativepart {
1458 0     0 0 0 my ($matrix) = @_;
1459 0         0 my $neg = new Math::MatrixSparse;
1460 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1461 0 0       0 next unless $matrix->elementkey($key) <0;
1462 0         0 $neg->assignkey($key,$matrix->{data}{$key});
1463             }
1464 0         0 $neg->{rows} = $matrix->{rows};
1465 0         0 $neg->{columns} = $matrix->{columns};
1466 0         0 return $neg;
1467             }
1468              
1469             sub _positivepart {
1470 0     0   0 my ($matrix) = @_;
1471 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1472 0 0       0 next if $matrix->elementkey($key) >0;
1473 0         0 $matrix->deletekey($key,$matrix->{data}{$key});
1474             }
1475 0         0 return $matrix;
1476             }
1477              
1478             sub _negativepart {
1479 0     0   0 my ($matrix) = @_;
1480 0         0 foreach my $key (keys %{$matrix->{data}}){
  0         0  
1481 0 0       0 next if $matrix->elementkey($key) <0;
1482 0         0 $matrix->deletekey($key,$matrix->{data}{$key});
1483             }
1484 0         0 return $matrix;
1485             }
1486              
1487              
1488             sub mask{
1489 0     0 0 0 my ($matrix, $i1,$i2,$j1,$j2) = @_;
1490 0 0 0     0 return undef unless (($i1<=$i2)&&($j1<=$j2));
1491 0         0 my $mask = new Math::MatrixSparse;
1492 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1493 0         0 my ($i,$j) = &splitkey($key);
1494 0 0 0     0 next unless (($i>=$i1)&&($i<=$i2));
1495 0 0 0     0 next unless (($j>=$j1)&&($j<=$j2));
1496 0         0 $mask->assignkey($key,$matrix->{data}{$key});
1497             }
1498 0         0 return $mask;
1499             }
1500              
1501             sub _mask{
1502 0     0   0 my ($matrix, $i1,$i2,$j1,$j2) = @_;
1503 0 0 0     0 return undef unless (($i1<=$i2)&&($j1<=$j2));
1504 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1505 0         0 my ($i,$j) = &splitkey($key);
1506 0 0 0     0 next if (($i>=$i1)&&($i<=$i2));
1507 0 0 0     0 next if (($j>=$j1)&&($j<=$j2));
1508 0         0 $matrix->assignkey($key,0);
1509             }
1510 0         0 return $matrix;
1511             }
1512              
1513             sub submatrix{
1514 0     0 0 0 my ($matrix, $i1,$i2,$j1,$j2) = @_;
1515 0 0 0     0 return undef unless (($i1<=$i2)&&($j1<=$j2));
1516 0         0 my $subm = new Math::MatrixSparse;
1517 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1518 0         0 my ($i,$j) = &splitkey($key);
1519 0 0 0     0 next unless (($i>=$i1)&&($i<=$i2));
1520 0 0 0     0 next unless (($j>=$j1)&&($j<=$j2));
1521 0         0 $subm->assign($i-$i1,$j-$j1,$matrix->{data}{$key});
1522             }
1523 0         0 return $subm;
1524             }
1525              
1526             sub insert {
1527 0     0 0 0 my ($big, $i0,$j0,$small) = @_;
1528 0         0 my $insert = $big->copy();
1529 0         0 foreach my $key (keys %{$small->{data}}){
  0         0  
1530 0         0 my ($i,$j) = &splitkey($key);
1531 0         0 $insert->assignkey($i+$i0,$j+$j0,$small->elementkey($key));
1532             }
1533 0         0 return $insert;
1534             }
1535              
1536             sub _insert {
1537 0     0   0 my ($big, $i0,$j0,$small) = @_;
1538            
1539 0         0 foreach my $key (keys %{$small->{data}}){
  0         0  
1540 0         0 my ($i,$j) = &splitkey($key);
1541 0         0 $big->assignkey($i+$i0,$j+$j0,$small->elementkey($key));
1542             }
1543 0         0 return $big;
1544             }
1545              
1546              
1547             sub shift {
1548 0     0 0 0 my ($matrix, $i1,$j1) = @_;
1549 0         0 my $this = new Math::MatrixSparse;
1550 0         0 $this->{name}=$matrix->{name};
1551 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1552 0         0 my ($i,$j) = &splitkey($key);
1553 0         0 $this->assign($i+$i1,$j+$j1,$matrix->{data}{$key});
1554             }
1555 0         0 return $this;
1556             }
1557              
1558             sub each {
1559 0     0 0 0 my ($matrix,$coderef) = @_;
1560 0         0 my $this = $matrix->copy();
1561 0         0 foreach my $key (keys %{$this->{data}}) {
  0         0  
1562 0         0 my ($i,$j) = &splitkey($key);
1563 0         0 $this->assign($i,$j,&$coderef($this->{data}{$key},$i,$j));
1564             }
1565 0         0 return $this;
1566             }
1567              
1568             sub _each {
1569 0     0   0 my ($matrix,$coderef) = @_;
1570 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1571 0         0 my ($i,$j) = &splitkey($key);
1572 0         0 $matrix->assign($i,$j,&$coderef($matrix->{data}{$key},$i,$j));
1573             }
1574 0         0 return $matrix;
1575             }
1576              
1577             ### INFORMATIONAL AND STATISTICAL METHODS
1578              
1579              
1580             sub printstats {
1581 0     0 0 0 my ($matrix,$name) = @_;
1582 0 0       0 return unless defined $matrix;
1583              
1584 0   0     0 $name = $name || $matrix->{name} || "unnamed matrix";
1585 0         0 print "Statistics for $name :\n";
1586 0         0 print "rows: $matrix->{rows}\t";
1587 0         0 print "columns: $matrix->{columns}\tdefined elements: ";
1588 0         0 print $matrix->count(), "\n";
1589 0         0 my ($width) = $matrix->width();
1590 0         0 print "Bandwidth: $width\t";
1591 0 0       0 print "Symmetric " if $matrix->is_symmetric();
1592 0 0       0 print "Skew-Symmetric " if $matrix->is_skewsymmetric();
1593             # print "Hermetian " if $matrix->{special}->{structure} =~m/^hermetian/i;
1594 0 0       0 print "Real " if $matrix->{special}->{field} =~ m/real/i;
1595             # print "Complex " if $matrix->{special}->{field} =~ m/complex/i;
1596 0 0       0 print "Strictly " if $matrix->{special}->{shape} =~ m/strict/;
1597 0 0       0 print "Upper Triangular " if $matrix->{special}->{shape} =~ m/upper/;
1598 0 0       0 print "Lower Triangular " if $matrix->{special}->{shape} =~ m/lower/;
1599 0 0       0 print "Diagonal" if $matrix->is_diagonal();
1600 0 0       0 print "Pattern " if $matrix->is_pattern();
1601 0 0       0 print "Square " if $matrix->is_square();
1602 0         0 print "\n";
1603             }
1604              
1605              
1606             sub dim {
1607 0     0 0 0 my ($matrix) = @_;
1608 0         0 return ($matrix->{rows},$matrix->{columns});
1609             }
1610              
1611             sub exactdim {
1612 2     2 0 4 my ($matrix) = @_;
1613 2         4 my $rows=0;
1614 2         4 my $columns = 0;
1615 2         5 foreach my $key (keys %{$matrix->{data}}) {
  2         160  
1616 871         1271 my ($i,$j) = &splitkey($key);
1617 871 100       1762 $rows = $i if $i>$rows;
1618 871 100       3616 $columns = $j if $j > $rows;
1619             }
1620 2         94 return ($rows,$columns);
1621             }
1622              
1623              
1624             sub count {
1625 0     0 0 0 my ($matrix) = @_;
1626 0         0 return scalar keys %{$matrix->{data}};
  0         0  
1627             }
1628              
1629             sub width {
1630 0     0 0 0 my ($matrix) = @_;
1631 0 0       0 return $matrix->{special}->{bandwidth} if $matrix->{special}->{bandwidth};
1632 0         0 my $width = 0;
1633 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1634 0         0 my ($i,$j) = &splitkey($key);
1635 0 0       0 $width = abs($i- $j) if abs($i-$j) > $width;
1636             }
1637 0         0 return ($width);
1638             }
1639              
1640             sub sum {
1641 1     1 0 2 my ($matrix) = @_;
1642 1         2 my $sum = 0;
1643 1         2 foreach my $key (keys %{$matrix->{data}}) {
  1         16  
1644 100         140 $sum += $matrix->elementkey($key);
1645             }
1646 1         13 return $sum;
1647             }
1648              
1649             sub sumeach {
1650 0     0 0 0 my ($matrix,$coderef) = @_;
1651 0         0 my $sum = 0;
1652 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1653 0         0 my ($i,$j) = &splitkey( $key);
1654 0         0 $sum += &$coderef($matrix->elementkey($key),$i,$j);
1655             }
1656 0         0 return $sum;
1657             }
1658              
1659             sub abssum {
1660 63     63 0 109 my ($matrix) = @_;
1661 63         144 my $sum = 0;
1662 63         110 foreach my $key (keys %{$matrix->{data}}) {
  63         620  
1663 1907         3121 $sum += abs($matrix->elementkey($key));
1664             }
1665 63         273 return $sum;
1666             }
1667              
1668             sub rownorm {
1669 0     0 0 0 my ($matrix) = @_;
1670 0         0 my %rowsums;
1671 0 0       0 return 0 unless defined $matrix;
1672 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1673 0         0 my ($i,$j) = &splitkey($key);
1674 0         0 $rowsums{$i}+= abs($matrix->elementkey($key));
1675             }
1676 0         0 return (sort {$a <=> $b} values %rowsums)[0];
  0         0  
1677             }
1678              
1679              
1680             sub columnnorm {
1681 0     0 0 0 my ($matrix) = @_;
1682 0         0 my %columnsums;
1683 0 0       0 return 0 unless defined $matrix;
1684 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1685 0         0 my ($i,$j) = &splitkey($key);
1686 0         0 $columnsums{$i}+= $matrix->elementkey($key);
1687             }
1688 0         0 return (sort {$a <=> $b} values %columnsums)[0];
  0         0  
1689             }
1690              
1691             sub norm_max {
1692 0     0 0 0 return $_[0]->rownorm();
1693             }
1694              
1695             sub norm_one {
1696 0     0 0 0 return $_[0]->columnnorm();
1697             }
1698              
1699             sub norm_frobenius {
1700 0     0 0 0 return sqrt($_[0]->sumeach(sub {$_[0]*$_[0]}));
  0     0   0  
1701             }
1702              
1703              
1704             sub trace {
1705 1     1 0 227 my ($matrix) = @_;
1706 1         6 return $matrix->diagpart()->sum();
1707             }
1708              
1709             ### BOOLEAN METHODS
1710              
1711             sub equals {
1712 10     10 0 385 my ($left,$right) = @_;
1713 10 50 33     35 return 1 unless ( defined $left|| defined $right);
1714 10 50       33 return 0 unless defined $left;
1715 10 50       25 return 0 unless defined $right;
1716 10         18 my $truth = 1;
1717 10         13 foreach my $key (keys %{$left->{data}}, keys %{$right->{data}}) {
  10         779  
  10         753  
1718 10096         15833 $truth *= ($left->elementkey($key) == $right->elementkey($key));
1719             }
1720 10         1073 return $truth;
1721             }
1722              
1723             sub is_square {
1724 3     3 0 5 my ($matrix) = @_;
1725 3 50       13 return 0 unless defined $matrix;
1726 3         17 return $matrix->{rows} == $matrix->{columns};
1727             }
1728             sub is_quadratic {
1729 0     0 0 0 my ($matrix) = @_;
1730 0 0       0 return 0 unless defined $matrix;
1731 0         0 return $matrix->{rows} == $matrix->{columns};
1732             }
1733              
1734             sub is_symmetric {
1735 1     1 0 4 my ($matrix) = @_;
1736 1 50       18 return 0 unless defined $matrix;
1737 1 50       19 return 1 if $matrix->{special}->{structure} =~ m/^symmetric/;
1738 0 0       0 return 0 if $matrix->{special}->{structure} =~ m/skewsymmetric/;
1739             # return 0 if $matrix->{special}->{structure} =~ m/hermetian/;
1740            
1741 0         0 my $truth = 1;
1742 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1743 0         0 my ($i,$j) = &splitkey($key);
1744 0         0 my $reversekey = &makekey($j, $i);
1745 0         0 $truth *= ($matrix->elementkey($key)
1746             == $matrix->elementkey($reversekey));
1747 0 0       0 return 0 unless $truth;
1748             }
1749 0         0 return $truth;
1750             }
1751              
1752             sub is_skewsymmetric {
1753 1     1 0 2 my ($matrix) = @_;
1754 1 50       5 return 0 unless defined $matrix;
1755 1 50       8 return 0 if $matrix->{special}->{structure} =~ m/^symmetric/;
1756 1 50       14 return 1 if $matrix->{special}->{structure} =~ m/^skewsymmetric/;
1757 0         0 my $truth = 1;
1758 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1759 0         0 my ($i,$j) = &splitkey($key);
1760 0         0 my $reversekey = &makekey($j , $i);
1761 0         0 $truth *= ($matrix->elementkey($key)
1762             == -1*$matrix->elementkey($reversekey));
1763 0 0       0 return 0 unless $truth;
1764             }
1765 0         0 return $truth;
1766             }
1767              
1768             sub is_diagonal {
1769 1     1 0 3 my ($matrix) = @_;
1770 1 50       7 return 0 unless defined $matrix;
1771 1 50       16 return 1 if $matrix->{special}->{shape}=~m/diagonal/i;
1772 0         0 my $truth = 1;
1773 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1774 0         0 my ($i,$j) = &splitkey($key);
1775 0         0 $truth *= ($i==$j);
1776 0 0       0 return 0 unless $truth;
1777             }
1778 0         0 return $truth;
1779             }
1780              
1781             sub is_strictlowertriangular {
1782 2     2 0 5 my ($matrix) = @_;
1783 2 50       9 return 0 unless defined $matrix;
1784 2 50       28 return 1 if $matrix->{special}->{shape}=~m/strictlower/i;
1785 0         0 my $truth = 1;
1786 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1787 0         0 my ($i,$j) = &splitkey($key);
1788 0         0 $truth *= ($i > $j);
1789 0 0       0 return 0 unless $truth;
1790             }
1791 0         0 return $truth;
1792             }
1793              
1794             sub is_strictuppertriangular {
1795 2     2 0 7 my ($matrix) = @_;
1796 2 50       10 return 0 unless defined $matrix;
1797 2 100       21 return 1 if $matrix->{special}->{shape}=~m/strictupper/i;
1798 1         2 my $truth = 1;
1799 1         2 foreach my $key (keys %{$matrix->{data}}) {
  1         33  
1800 275         6381 my ($i,$j) = &splitkey($key);
1801 275         416 $truth *= ($i < $j);
1802 275 50       613 return 0 unless $truth;
1803             }
1804 1         26 return $truth;
1805             }
1806              
1807             sub is_lowertriangular {
1808 2     2 0 5 my ($matrix) = @_;
1809 2 50       9 return 0 unless defined $matrix;
1810 2         5 my $truth = 1;
1811 2 50       25 return 1 if $matrix->{special}->{shape}=~m/lower/i;
1812 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1813 0         0 my ($i,$j) = &splitkey($key);
1814 0         0 $truth *= ($i >= $j);
1815 0 0       0 return 0 unless $truth;
1816             }
1817 0         0 return $truth;
1818             }
1819              
1820             sub is_uppertriangular {
1821 2     2 0 402 my ($matrix) = @_;
1822 2 50       10 return 0 unless defined $matrix;
1823 2         5 my $truth = 1;
1824 2 50       25 return 1 if $matrix->{special}->{shape}=~m/upper/i;
1825 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1826 0         0 my ($i,$j) = &splitkey($key);
1827 0         0 $truth *= ($i <= $j);
1828 0 0       0 return 0 unless $truth;
1829             }
1830 0         0 return $truth;
1831             }
1832              
1833             sub is_positive {
1834 0     0 0 0 my ($matrix) = @_;
1835 0 0       0 return 0 unless defined $matrix;
1836 0 0       0 return 1 if $matrix->{special}->{sign} =~ m/^positive/i;
1837 0         0 my $truth = 1;
1838 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1839 0         0 $truth *= ($matrix->elementkey($key)>0);
1840 0 0       0 return 0 unless $truth;
1841             }
1842 0         0 return $truth;
1843             }
1844              
1845             sub is_zero {
1846 0     0 0 0 my ($matrix) = @_;
1847 0 0       0 return 0 unless defined $matrix;
1848 0         0 my $truth = 1;
1849 0 0       0 return 1 if $matrix->{special}->{sign} =~ /zero/i;
1850 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1851 0         0 $truth *= ($matrix->elementkey($key)==0);
1852 0 0       0 return 0 unless $truth;
1853             }
1854 0         0 return $truth;
1855             }
1856              
1857              
1858             sub is_nonpositive {
1859 0     0 0 0 my ($matrix) = @_;
1860 0         0 my $truth = 1;
1861 0 0       0 return 0 unless defined $matrix;
1862 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1863 0         0 $truth *= ($matrix->elementkey($key)<=0);
1864 0 0       0 return 0 unless $truth;
1865             }
1866 0         0 return $truth;
1867             }
1868              
1869             sub is_negative {
1870 0     0 0 0 my ($matrix) = @_;
1871 0         0 my $truth = 1;
1872 0 0       0 return 0 unless defined $matrix;
1873 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1874 0         0 $truth *= ($matrix->elementkey($key)<0);
1875 0 0       0 return 0 unless $truth;
1876             }
1877 0         0 return $truth;
1878             }
1879              
1880             sub is_nonnegative {
1881 0     0 0 0 my ($matrix) = @_;
1882 0         0 my $truth = 1;
1883 0 0       0 return 0 unless defined $matrix;
1884 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1885 0         0 $truth *= ($matrix->elementkey($key)>=0);
1886 0 0       0 return 0 unless $truth;
1887             }
1888 0         0 return $truth;
1889             }
1890              
1891             sub is_boolean {
1892 0     0 0 0 my ($matrix) = @_;
1893 0 0       0 return 0 unless defined $matrix;
1894 0 0       0 return 1 if $matrix->{special}->{pattern};
1895 0         0 my $truth = 1;
1896 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1897 0   0     0 $truth *= (($matrix->elementkey($key) == 0)||($matrix->elementkey($key) == 1));
1898 0 0       0 return 0 unless $truth;
1899             }
1900 0         0 return $truth;
1901             }
1902              
1903             sub is_pattern {
1904 0     0 0 0 my ($matrix) = @_;
1905 0 0       0 return 0 unless defined $matrix;
1906 0 0       0 return 1 if $matrix->{special}->{pattern};
1907 0         0 my $truth = 1;
1908 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1909 0   0     0 $truth *= (($matrix->elementkey($key) == 0)||($matrix->elementkey($key) == 1));
1910 0 0       0 return 0 unless $truth;
1911             }
1912 0         0 return $truth;
1913             }
1914              
1915              
1916             sub diagnose {
1917 0     0 0 0 my ($matrix) = @_;
1918 0 0       0 return 0 unless defined $matrix;
1919 0         0 my $boolean = 1;
1920 0         0 $matrix->_sizetofit();
1921 0         0 $matrix->{special}->{square} = ($matrix->{rows} == $matrix->{columns});
1922 0         0 my $upper = 1;
1923 0         0 my $diagonal = 1;
1924 0         0 my $lower = 1;
1925 0         0 my $strictupper = 1;
1926 0         0 my $strictlower = 1;
1927 0         0 my $positive = 1;
1928 0         0 my $nonpositive = 1;
1929 0         0 my $negative = 1;
1930 0         0 my $nonnegative = 1;
1931 0         0 my $zero = 1;
1932 0         0 my $symmetric = 1;
1933 0         0 my $skewsymmetric = 1;
1934 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
1935 0         0 my ($i,$j) = &splitkey($key);
1936 0         0 my $reversekey = &makekey($j,$i);
1937 0         0 $symmetric *= ($matrix->elementkey($key)
1938             == $matrix->elementkey($reversekey));
1939 0         0 $skewsymmetric *= ($matrix->elementkey($key)
1940             == -1*$matrix->elementkey($reversekey));
1941 0         0 $diagonal *= ($i==$j);
1942 0         0 $strictlower *= ($i > $j);
1943 0         0 $lower *= ($i >= $j);
1944 0         0 $strictupper *= ($i > $j);
1945 0         0 $upper *= ($i >= $j);
1946 0         0 $positive *= ($matrix->elementkey($key)>0);
1947 0         0 $nonpositive *= ($matrix->elementkey($key)<=0);
1948 0         0 $negative *= ($matrix->elementkey($key)<0);
1949 0         0 $nonnegative *= ($matrix->elementkey($key)>=0);
1950 0   0     0 $boolean *= (($matrix->elementkey($key) == 0)
1951             ||($matrix->elementkey($key) == 1));
1952 0         0 $zero *= !($matrix->elementkey($key));
1953             }
1954 0         0 $matrix->{special}->{pattern} = $boolean;
1955 0 0       0 if ($diagonal) {
    0          
    0          
1956 0         0 $matrix->{special}->{shape} = "diagonal";
1957             } elsif ($lower) {
1958 0 0       0 if ($strictlower) {
1959 0         0 $matrix->{special}->{shape} = "strictlower";
1960             } else {
1961 0         0 $matrix->{special}->{shape} = "lower";
1962             }
1963             } elsif ($upper) {
1964 0 0       0 if ($strictlower) {
1965 0         0 $matrix->{special}->{shape} = "strictupper";
1966             } else {
1967 0         0 $matrix->{special}->{shape} = "upper";
1968             }
1969             } else {
1970 0         0 $matrix->{special}->{shape}="";
1971             }
1972 0 0       0 if ($symmetric) {
    0          
1973 0         0 $matrix->{special}->{structure}="symmetric";
1974             } elsif ($skewsymmetric) {
1975 0         0 $matrix->{special}->{structure}="skewsymmetric";
1976             } else {
1977 0         0 $matrix->{special}->{structure}="";
1978             }
1979 0 0       0 if ($zero) {
    0          
    0          
1980 0         0 $matrix->{special}->{sign} = "zero";
1981             } elsif ($nonpositive) {
1982 0 0       0 if ($negative) {
1983 0         0 $matrix->{special}->{sign}="negative";
1984             } else {
1985 0         0 $matrix->{special}->{sign}="nonpositive";
1986             }
1987             } elsif ($nonnegative) {
1988 0 0       0 if ($positive) {
1989 0         0 $matrix->{special}->{sign}="positive";
1990             } else {
1991 0         0 $matrix->{special}->{sign}="nonnegative";
1992             }
1993             } else {
1994 0         0 $matrix->{special}->{sign}="";
1995             }
1996 0         0 return $matrix;
1997             }
1998              
1999              
2000             ### BOOLEAN ARITHMETIC METHODS
2001              
2002             sub matrixand {
2003 0     0 0 0 my ($matrix1, $matrix2) = @_;
2004 0         0 my $truth = new Math::MatrixSparse;
2005 0 0       0 $truth->{rows} = ($matrix1->{rows}<$matrix2->{rows}) ? $matrix1->{rows} : $matrix2->{rows};
2006 0 0       0 $truth->{columns} = ($matrix1->{columns}<$matrix2->{columns}) ? $matrix1->{columns} : $matrix2->{columns};
2007 0         0 foreach my $key (keys %{$matrix1->{data}}) {
  0         0  
2008 0   0     0 $truth->assignkey($key,$matrix1->elementkey($key)&&
2009             $matrix2->elementkey($key));
2010             }
2011 0 0 0     0 if ((defined $matrix1->{name})&&(defined $matrix2->{name})) {
2012 0         0 $truth->{name} = $matrix1->{name} . "&" . $matrix2->{name};
2013             }
2014             #should be updated already.
2015 0         0 $truth->{special}->{pattern} = 1;
2016 0         0 return $truth;
2017             }
2018              
2019             sub matrixor {
2020 0     0 0 0 my ($matrix1, $matrix2) = @_;
2021 0         0 my $truth = new Math::MatrixSparse;
2022 0 0       0 $truth->{rows} = ($matrix1->{rows}<$matrix2->{rows}) ? $matrix1->{rows} : $matrix2->{rows};
2023 0 0       0 $truth->{columns} = ($matrix1->{columns}<$matrix2->{columns}) ? $matrix1->{columns} : $matrix2->{columns};
2024 0         0 foreach my $key (keys %{$matrix1->{data}}) {
  0         0  
2025 0   0     0 $truth->assignkey($key,$matrix1->elementkey($key)||
2026             $matrix2->elementkey($key));
2027             }
2028 0 0 0     0 if ((defined $matrix1->{name})&&(defined $matrix2->{name})) {
2029 0         0 $truth->{name} = $matrix1->{name} . "|" . $matrix2->{name};
2030             }
2031             #should be updated already.
2032 0         0 $truth->{special}->{pattern} = 1;
2033 0         0 return $truth;
2034             }
2035              
2036              
2037             ### DELETION FUNCTIONS
2038             sub delete {
2039 4316     4316 0 6933 my ($matrix,$i,$j) = @_;
2040 4316         6880 my $key = &makekey($i,$j);
2041 4316         24407 $matrix->deletekey($key);
2042 4316         7483 return;
2043             }
2044              
2045             sub deletekey {
2046 4316     4316 0 5494 my ($matrix, $key) = @_;
2047 4316         14369 my $old = $matrix->elementkey($key);
2048 4316         7784 my ($i,$j) = &splitkey($key);
2049 4316         9126 delete $matrix->{data}{$key};
2050             #sign
2051 4316 50       15801 if ($matrix->{special}->{sign} =~ /^positive/i) {
    50          
2052 0         0 $matrix->{special}->{sign} = "nonnegative";
2053             } elsif ($matrix->{special}->{sign} =~ /^positive/i) {
2054 0         0 $matrix->{special}->{sign} = "nonpositive";
2055             }
2056             #structure
2057 4316 50 33     11085 if (
2058             ($matrix->{special}->{structure} =~ m/symmetric/i)
2059             &&($i!=$j)
2060             ) {
2061 0         0 $matrix->{special}->{structure} = "";
2062             }
2063             #band
2064 4316 100       12306 if (abs($i-$j) >= $matrix->{special}->{bandwidth}) {
2065 3939         6188 $matrix->{special}->{bandwidth} = 0;
2066             }
2067             #pattern--no change necessary
2068             #shape--no change necessary
2069             #persistent row and column data
2070 4316         5443 delete $matrix->{sortedrows};
2071 4316         5277 delete $matrix->{sortedcolumns};
2072 4316         6104 return undef;
2073             }
2074              
2075             sub cull {
2076 0     0 0 0 my ($matrix) = @_;
2077 0         0 my $this = $matrix->copy();
2078 0         0 foreach my $key (keys %{$this->{data}}) {
  0         0  
2079 0 0       0 next if $this->{data}{$key};
2080 0         0 $this->deletekey($key);
2081             }
2082 0         0 return $this;
2083             }
2084              
2085             sub _cull {
2086 0     0   0 my ($matrix) = @_;
2087 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2088 0 0       0 next if $matrix->{data}{$key};
2089 0         0 $matrix->deletekey($key);
2090             }
2091 0         0 return $matrix;
2092             }
2093              
2094             sub threshold {
2095 0     0 0 0 my ($matrix,$thresh) = @_;
2096 0 0       0 return undef if $thresh <0;
2097 0         0 my $this = $matrix->copy();
2098 0         0 foreach my $key (keys %{$this->{data}}) {
  0         0  
2099 0 0       0 next if abs($this->{data}{$key})>$thresh;
2100 0         0 $this->deletekey($key);
2101             }
2102 0         0 return $this;
2103             }
2104              
2105             sub _threshold {
2106 0     0   0 my ($matrix,$thresh) = @_;
2107 0 0       0 return undef if $thresh <0;
2108 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2109 0 0       0 next if abs($matrix->{data}{$key})>$thresh;
2110 0         0 $matrix->deletekey($key);
2111             }
2112 0         0 return $matrix;
2113             }
2114              
2115             sub sizetofit {
2116 0     0 0 0 my ($matrix) = @_;
2117 0         0 my $this = $matrix->copy();
2118 0         0 my ($maxrow,$maxcol) = $this->exactdim();
2119 0         0 $this->{rows} = $maxrow;
2120 0         0 $this->{columns} = $maxcol;
2121 0         0 return $this;
2122             }
2123              
2124              
2125             sub _sizetofit {
2126 2     2   6 my ($matrix) = @_;
2127 2         12 my ($maxrow,$maxcol) = $matrix->exactdim();
2128 2         13 $matrix->{rows} = $maxrow;
2129 2         7 $matrix->{columns} = $maxcol;
2130 2         9 return $matrix;
2131             }
2132              
2133              
2134             sub clearspecials {
2135 0     0 0 0 my ($matrix) = @_;
2136 0         0 $matrix->{special}->{pattern} = 0;
2137 0         0 $matrix->{special}->{sign} = "";
2138 0         0 $matrix->{special}->{structure} = "";
2139 0         0 $matrix->{special}->{shape} = "";
2140             }
2141              
2142             ### SOLVER METHODS
2143              
2144             sub gaussseidel {
2145             #solves Ax=b by Gauss-Seidel, or SOR with relaxation parameter 1.
2146             #b ($constant) and x0 ($guess) should be column vectors.
2147 1     1 0 351 my ($matrix, $constant, $guess,$tol, $steps) = @_;
2148 1         25 return $matrix->SOR($constant,$guess,1,$tol,$steps);
2149             }
2150              
2151             sub SOR {
2152             #solves Ax=b by Symmetric Over-Relaxation
2153             #b ($constant) and x0 ($guess) should be column vectors.
2154 2     2 0 376 my ($matrix, $constant, $guess, $omega,$tol, $steps) = @_;
2155 2         12 my $diag = $matrix->diagpart();
2156 2         9 my $lower = $matrix->lowerpart();
2157 2         10 my $upper = $matrix->upperpart();
2158 2         4 my $iterator;
2159 2         9 my $dinv = $diag->terminvert();
2160 2         6 my $soln = $guess->copy();
2161 2         4 foreach my $key (keys %{$constant->{data}}) {
  2         10  
2162 20 50       44 next if defined $soln->{data}{$key};
2163 0         0 $soln->{data}{$key} = 0;
2164             }
2165 2         10 my @lowerkeys = $lower->sortbyrow();
2166 2         5 my @upperkeys = $upper->sortbyrow();
2167 2         23 my @solnkeys = $soln->sortbyrow();
2168 2 50       10 $steps = 100 unless $steps;
2169 2         7 for my $k (1 .. $steps) {
2170 54         194 my $oldsoln = $soln->copy();
2171 54         72 foreach my $thissolnkey (keys %{$soln->{data}}) {
  54         310  
2172 1728         3353 my ($i,$j) = &splitkey($thissolnkey);
2173 1728 50       4427 next unless $j == 1;
2174 1728         3966 my $prev = $oldsoln->{data}{$thissolnkey}*(1.0-$omega);
2175 1728         3949 my $scal = $omega * $dinv->element($i,$i);
2176 1728         2014 my $lowersum=0;
2177 1728         4148 my $uppersum = 0;
2178 1728         3217 foreach my $lowerkey (@lowerkeys) {
2179 0         0 my ($li,$lj) = &splitkey($lowerkey);
2180             #unnecessary b/c of source of @lowerkeys
2181 0 0       0 next if $lj > ($i-1);
2182             # print "L $li $lj\n";
2183 0         0 $lowersum += $soln->element($lj,1)*$lower->{data}{$lowerkey};
2184             }
2185 1728         2271 foreach my $upperkey (@upperkeys) {
2186 84672         154494 my ($ui,$uj) = &splitkey($upperkey);
2187             #unnecessary b/c of source of @upperkeys
2188 84672 100       204992 next if $uj < ($i+1);
2189 50814         118146 $uppersum += $soln->element($uj,1)*$upper->{data}{$upperkey};
2190             }
2191 1728         4056 my $update = $prev+$scal*($constant->element($i,1)-$lowersum-$uppersum);
2192 1728         4404 $soln->assign($i,1,$update);
2193             }
2194 54         672 my $err = &abssum($oldsoln-$soln);
2195 54 100       872 return $soln if $err<$tol;
2196             }
2197 0         0 return undef;
2198             }
2199              
2200              
2201             sub jacobi {
2202             #solves Ax=b by Jacobi iteration.
2203             #Note that b doesn't have to be a column vector
2204             #$guess (x0) should be the same size as $constant (b)
2205 1     1 0 8 my ($matrix, $constant, $guess,$tol, $steps) = @_;
2206 1         6 my $diag = $matrix->diagpart();
2207 1         6 my $lower = $matrix->lowerpart();
2208 1         5 my $upper = $matrix->upperpart();
2209 1         2 my $iterator;
2210 1         5 my $dinv = $diag->terminvert();
2211 1         8 my $nondiag = ($lower+$upper);
2212 1         4 my $soln = $guess->copy();
2213 1         3 my $oldsoln;
2214 1 50       4 $steps = 100 unless $steps;
2215 1         4 for my $i (1 .. $steps) {
2216 9         30 $oldsoln=$soln->copy();;
2217 9         82 $soln = $dinv*$constant - $dinv*$nondiag*$soln;
2218 9         184 my $err = &abssum($oldsoln-$soln);
2219 9 100       252 return $soln if $err<$tol;
2220             }
2221 0         0 return undef;
2222             }
2223              
2224              
2225             ### SORTING METHODS
2226              
2227             #note: column is a secondary sort criterion
2228             # 1 2 3
2229             # 4 5 6
2230             # 7 8 9
2231             sub sortbyrow {
2232 49     49 0 78 my ($matrix) = @_;
2233 9426 50       17571 my @sorted = map { $_->[0] }
  81202         153984  
2234 9426         14142 sort { ($a->[1] <=> $b->[1])||($a->[2]<=>$b->[2]) }
2235 49         82 map { [ $_, &splitkey($_) ] } keys %{$matrix->{data}};
  49         1655  
2236 49         6533 return @sorted;
2237             }
2238              
2239             #note: row is a secondary sort criterion
2240             # 1 4 7
2241             # 2 5 8
2242             # 3 6 9
2243             sub sortbycolumn {
2244 43     43 0 68 my ($matrix) = @_;
2245 10301 50       17259 my @sorted = map { $_->[0] }
  89185         157220  
2246 10301         15978 sort { ($a->[2] <=> $b->[2])||($a->[1]<=>$b->[1]) }
2247 43         58 map { [ $_, &splitkey($_) ] } keys %{$matrix->{data}};
  43         2148  
2248            
2249 43         6948 return @sorted;
2250             }
2251              
2252             #note: row is a secondary sort criterion
2253             #lower diagonals are sorted in front of higher ones
2254             #4 7 9
2255             #2 5 8
2256             #1 3 6
2257             sub sortbydiagonal {
2258 0     0 0 0 my ($matrix) = @_;
2259 0 0       0 my @sorted = map { $_->[0] }
  0         0  
2260 0         0 sort { (($a->[1]-$a->[2]) <=> ($b->[1]-$b->[2]))||($a->[1]<=>$b->[1]) }
2261 0         0 map { [ $_, &splitkey($_) ] } keys %{$matrix->{data}};
  0         0  
2262            
2263 0         0 return @sorted;
2264             }
2265              
2266             #sorts the elements of $matrix by value, smallest first.
2267             #row is a secondary criterion, and column is tertiaty.
2268             sub sortbyvalue {
2269 0     0 0 0 my ($matrix) = @_;
2270 0 0 0     0 my @sorted = map { $_->[0] }
  0         0  
2271             sort {
2272 0         0 ($matrix->elementkey($a->[0]) <=> $matrix->elementkey($a->[0]))
2273             || ($a->[1]<=>$b->[1])
2274             ||($a->[2]<=>$b->[2])
2275             }
2276 0         0 map { [ $_, &splitkey($_) ] } keys %{$matrix->{data}};
  0         0  
2277            
2278 0         0 return @sorted;
2279             }
2280              
2281              
2282             ### SYMMETRIC METHODS
2283             sub symmetrify {
2284             #takes a matrix, returns the matrix obtained by reflecting
2285             #non-lower part around main diagonal
2286 1     1 0 364 my ($matrix) = @_;
2287 1 50       34 return $matrix if $matrix->{special}->{shape} =~ /diagonal/i;
2288 1         5 my $this = $matrix->copy();
2289 1         6 $this->_symmetrify();
2290 1         10 return $this;
2291             }
2292              
2293              
2294             sub _symmetrify {
2295             #takes a matrix, reflects it about main diagonal
2296 2     2   4 my ($matrix) = @_;
2297 2 50       11 return $matrix if $matrix->{special}->{shape} =~ /diagonal/i;
2298 2         13 $matrix->_nonlowerpart();
2299 2         3 foreach my $key (keys %{$matrix->{data}}) {
  2         162  
2300 646         1029 my ($i,$j) = &splitkey($key);
2301 646 100       1662 unless ($i==$j) {
2302 225         352 my $transkey = &makekey($j , $i);
2303 225         564 $matrix->assign($j,$i,$matrix->element($i,$j));
2304             }
2305             }
2306 2         355 $matrix->_sizetofit();
2307 2         9 $matrix->{special}->{shape} = "";
2308 2         7 $matrix->{special}->{structure} = "symmetric";
2309 2         5 return $matrix;
2310             }
2311              
2312             sub skewsymmetrify {
2313             #takes a matrix, returns the matrix obtained by reflecting
2314             #upper part around main diagonal
2315 1     1 0 550 my ($matrix) = @_;
2316 1         8 my $this = $matrix->copy();
2317 1         7 return $this->_skewsymmetrify();
2318             }
2319              
2320             sub _skewsymmetrify {
2321             #takes a matrix, reflects upper part about main diagonal
2322 1     1   2 my ($matrix) = @_;
2323 1         5 $matrix->_upperpart();
2324 1         2 foreach my $key (keys %{$matrix->{data}}) {
  1         32  
2325 225         375 my ($i,$j) = &splitkey($key);
2326 225         379 my $transkey = &makekey($j , $i);
2327 225         517 $matrix->assign($j,$i,-1*$matrix->element($i,$j));
2328             }
2329 1         42 $matrix->{special}->{shape} = "";
2330 1         2 $matrix->{special}->{structure} = "skewsymmetric";
2331 1         9 return $matrix;
2332             }
2333              
2334              
2335             ### PROBABILISTIC METHODS
2336             sub normalize {
2337 0     0 0 0 my ($matrix) = @_;
2338 0 0       0 return undef unless defined $matrix;
2339 0         0 my $name = $matrix->{name};
2340 0 0       0 unless ($matrix->is_nonnegative()) {
2341 0         0 carp "Math::MatrixSparse::normalize matrix has negative elements";
2342 0         0 return undef;
2343             }
2344 0         0 my $matsum = 0;
2345 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2346 0         0 $matsum += $matrix->elementkey($key);
2347             }
2348 0 0       0 $matrix= $matrix->multiplyscalar(1.0/$matsum) if $matsum;
2349 0 0       0 $matrix->name($name . "/||" . $name . "||") if ($name);
2350 0         0 return $matrix;
2351             }
2352              
2353             sub _normalize {
2354            
2355 0     0   0 my ($matrix) = @_;
2356 0 0       0 return undef unless defined $matrix;
2357 0         0 my $name = $matrix->{name};
2358 0 0       0 unless ($matrix->is_nonnegative()) {
2359 0         0 carp "Math::MatrixSparse::normalize matrix has negative elements";
2360 0         0 return undef;
2361             }
2362 0         0 my $matsum = 0;
2363 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2364 0         0 $matsum += $matrix->elementkey($key);
2365             }
2366 0 0       0 $matrix= $matrix->_multiplyscalar(1.0/$matsum) if $matsum;
2367 0 0       0 $matrix->name($name . "/||" . $name . "||") if ($name);
2368 0         0 return $matrix;
2369             }
2370              
2371              
2372             sub normalizerows {
2373 0     0 0 0 my ($matrix) = @_;
2374 0         0 my %rowsums;
2375 0 0       0 return undef unless defined $matrix;
2376 0 0       0 unless ($matrix->is_nonnegative()) {
2377 0         0 carp "Math::MatrixSparse::normalizerows matrix has negative elements";
2378 0         0 return undef;
2379             }
2380 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2381 0         0 my ($i,$j) = &splitkey($key);
2382 0         0 $rowsums{$i}+= $matrix->elementkey($key);
2383             }
2384 0         0 my $rownormed = $matrix->cull();
2385 0         0 foreach my $key (keys %{$rownormed->{data}}) {
  0         0  
2386 0         0 my ($i,$j) = &splitkey($key);
2387 0 0       0 next unless $rowsums{$i};
2388 0         0 $rownormed->assign($i,$j,($rownormed->elementkey($key))/$rowsums{$i});
2389             }
2390 0         0 my $name = $matrix->{name};
2391 0 0       0 $rownormed->name($name . "/||" . $name . "||") if ($name);
2392 0         0 return $rownormed;
2393             }
2394              
2395              
2396              
2397             sub _normalizerows {
2398 0     0   0 my ($matrix) = @_;
2399 0         0 my %rowsums;
2400 0 0       0 return undef unless defined $matrix;
2401 0 0       0 unless ($matrix->is_nonnegative()) {
2402 0         0 carp "Math::MatrixSparse::normalizerows matrix has negative elements";
2403 0         0 return undef;
2404             }
2405 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2406 0         0 my ($i,$j) = &splitkey($key);
2407 0         0 $rowsums{$i}+= $matrix->{data}{$key};
2408             }
2409 0         0 $matrix->_cull();
2410 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2411 0         0 my ($i,$j) = &splitkey($key);
2412 0 0       0 next unless $rowsums{$i};
2413 0         0 $matrix->assign($i,$j,($matrix->elementkey($key))/$rowsums{$i});
2414             }
2415 0         0 my $name = $matrix->{name};
2416 0 0       0 $matrix->name($name . "/||" . $name . "||") if ($name);
2417 0         0 return $matrix;
2418             }
2419              
2420             sub normalizecolumns {
2421 0     0 0 0 my ($matrix) = @_;
2422 0         0 my %columnsums;
2423 0 0       0 return undef unless defined $matrix;
2424 0 0       0 unless ($matrix->is_nonnegative()) {
2425 0         0 carp "Math::MatrixSparse::normalizecolumns matrix has negative elements";
2426 0         0 return undef;
2427             }
2428 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2429 0         0 my ($i,$j) = &splitkey($key);
2430 0         0 $columnsums{$j}+= $matrix->elementkey($key);
2431             }
2432 0         0 my $columnnormed = $matrix->copy();
2433 0         0 foreach my $key (keys %{$columnnormed->{data}}) {
  0         0  
2434 0         0 my ($i,$j) = &splitkey($key);
2435 0         0 $columnnormed->assign($i,$j,$columnnormed->elementkey($key)/$columnsums{$j});
2436             }
2437 0         0 my $name = $matrix->{name};
2438 0 0       0 $columnnormed->name($name . "/||" . $name . "||") if ($name);
2439 0         0 return $columnnormed;
2440             }
2441              
2442              
2443             sub _normalizecolumns {
2444 0     0   0 my ($matrix) = @_;
2445 0         0 my %columnsums;
2446 0 0       0 return undef unless defined $matrix;
2447 0 0       0 unless ($matrix->is_nonnegative()) {
2448 0         0 carp "Math::MatrixSparse::normalizecolumns matrix has negative elements";
2449 0         0 return undef;
2450             }
2451 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2452 0         0 my ($i,$j) = &splitkey($key);
2453 0         0 $columnsums{$j}+= $matrix->elementkey($key);
2454             }
2455 0         0 $matrix->_cull();
2456 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2457 0         0 my ($i,$j) = &splitkey($key);
2458 0         0 $matrix->assign($i,$j,$matrix->elementkey($key)/$columnsums{$j});
2459             }
2460 0         0 my $name = $matrix->{name};
2461 0 0       0 $matrix->name($name . "/||" . $name . "||") if ($name);
2462 0         0 return $matrix;
2463             }
2464              
2465              
2466             sub discretepdf {
2467 0     0 0 0 my ($matrix) = @_;
2468 0         0 my $uniform = rand;
2469              
2470 0         0 my $current;
2471 0         0 my $curpos = 0;
2472 0         0 foreach my $key (keys %{$matrix->{data}}) {
  0         0  
2473 0 0       0 last if $curpos >=$uniform;
2474 0         0 $current = $key;
2475 0         0 $curpos += $matrix->{data}{$key};
2476             }
2477 0         0 return $current;
2478              
2479             }
2480              
2481             ### KEY-INTERFACE METHODS
2482              
2483             sub splitkey {
2484 375135     375135 0 441394 my ($key) = @_;
2485 375135         816049 my ($i,$j) = split(/\0/,$key);
2486 375135         939987 return ($i,$j);
2487             }
2488              
2489             sub makekey {
2490 290224     290224 0 423785 my ($i,$j) = @_;
2491 290224 50 33     1509338 return ($i . "\0" . $j) if ((defined $i)&&(defined $j));
2492              
2493             }
2494              
2495              
2496             1;
2497             __END__