File Coverage

blib/lib/Math/Matrix/Banded/Square.pm
Criterion Covered Total %
statement 195 215 90.7
branch 23 26 88.4
condition 19 24 79.1
subroutine 19 22 86.3
pod 0 9 0.0
total 256 296 86.4


line stmt bran cond sub pod time code
1             package Math::Matrix::Banded::Square;
2 8     8   127 use 5.014;
  8         30  
3              
4             =pod
5              
6             =head1 NAME
7              
8             Math::Matrix::Banded::Square - banded square matrix
9              
10             =head1 VERSION
11              
12             Version 0.004
13              
14             =cut
15              
16             our $VERSION = '0.004';
17              
18 8     8   4541 use Moo;
  8         92504  
  8         35  
19             use List::Util (
20 8         668 'min',
21             'max',
22 8     8   11915 );
  8         28  
23 8     8   4358 use Try::Tiny;
  8         10412  
  8         20569  
24              
25              
26             =pod
27              
28             =head1 SYNOPSIS
29              
30             Quick summary of what the module does.
31              
32             Perhaps a little code snippet.
33              
34             use Math::Matrix::Banded;
35              
36             my $foo = Math::Matrix::Banded->new();
37             ...
38              
39             =head1 DESCRIPTION
40              
41              
42             =head1 ATTRIBUTES
43              
44             =head3 data
45              
46             =cut
47              
48             has 'N' => (
49             is => 'ro',
50             required => 1,
51             );
52              
53             has 'm_below' => (
54             is => 'rwp',
55             lazy => 1,
56             default => 0,
57             );
58              
59             has 'm_above' => (
60             is => 'rwp',
61             lazy => 1,
62             default => 0,
63             );
64              
65             has 'symmetric' => (
66             is => 'lazy',
67             default => 0,
68             );
69              
70             has '_data' => (
71             is => 'lazy',
72             builder => sub {
73 35     35   252 my ($self) = @_;
74 35         85 my $N = $self->N;
75 35         577 my $m = $self->m_below + 1 + $self->m_above;
76 35         844 my $data = [];
77              
78 35         104 for (my $i=0;$i<$N;$i++) {
79 209         354 push(@$data, [map { 0 } (1..$m)]);
  440         898  
80             }
81              
82 35         119 return $data;
83             },
84             );
85              
86             has '_LU_raw' => (
87             is => 'rwp',
88             lazy => 1,
89             builder => '_decompose_LU_without_pivoting',
90             clearer => 1,
91             predicate => 1,
92             trigger => sub {
93             my ($self) = @_;
94              
95             $self->clear_L;
96             $self->clear_U;
97             $self->clear_permutation;
98             },
99             );
100              
101             has 'L' => (
102             is => 'lazy',
103             builder => sub {
104 4     4   1275 my ($self) = @_;
105              
106 4         65 return $self->_LU_raw->[0];
107             },
108             clearer => 1,
109             predicate => 1,
110             );
111              
112             has 'U' => (
113             is => 'lazy',
114             builder => sub {
115 4     4   2980 my ($self) = @_;
116              
117 4         65 return $self->_LU_raw->[1];
118             },
119             clearer => 1,
120             predicate => 1,
121             );
122              
123             has 'permutation' => (
124             is => 'lazy',
125             builder => sub {
126 1     1   927 my ($self) = @_;
127              
128 1         17 return $self->_LU_raw->[2];
129             },
130             clearer => 1,
131             predicate => 1,
132             );
133              
134             =pod
135              
136             =head1 METHODS
137              
138             =cut
139              
140             sub element {
141 1083     1083 0 127734 my ($self, $i, $j, @args) = @_;
142 1083         18921 my $m_below = $self->m_below;
143 1083         21472 my $m_above = $self->m_above;
144 1083         20864 my $data = $self->_data;
145              
146 1083         6733 my $j_eff_max = $m_below + $m_above;
147 1083         1735 my $j_eff = $j + $m_below - $i;
148 1083 100       2458 if (@args > 0) {
149 569 100       1264 if ($j_eff < 0) {
    100          
150 26         81 $self->_increase_m_below(-$j_eff);
151 26         213 $j_eff_max -= $j_eff;
152 26         41 $j_eff = 0;
153             }
154             elsif ($j_eff > $j_eff_max) {
155 23         78 $self->_increase_m_above($j_eff - $j_eff_max);
156 23         185 $j_eff_max = $j_eff;
157             }
158 569         896 $data->[$i]->[$j_eff] = $args[0];
159              
160 569 100 100     8874 if (!$args[1] and $self->symmetric and $i != $j) {
      100        
161 25         242 $self->element($j, $i, $args[0], 1);
162             }
163             }
164              
165 1083 100 100     6683 if ($j_eff < 0 or $j_eff > $j_eff_max) { return 0 }
  246         1040  
166 837   50     3673 else { return($data->[$i]->[$j_eff] // 0) }
167             }
168              
169              
170             sub _increase_m_below {
171 26     26   54 my ($self, $k) = @_;
172 26         402 my $data = $self->_data;
173              
174 26         188 my @zeroes = map { 0 } (1..$k);
  26         65  
175 26         59 foreach my $row (@$data) {
176 159         291 unshift(@$row, @zeroes);
177             }
178 26         436 $self->_set_m_below($self->m_below + $k);
179             }
180              
181              
182             sub _increase_m_above {
183 23     23   48 my ($self, $k) = @_;
184 23         362 my $data = $self->_data;
185              
186 23         175 my @zeroes = map { 0 } (1..$k);
  23         62  
187 23         54 foreach my $row (@$data) {
188 138         246 push(@$row, @zeroes);
189             }
190 23         406 $self->_set_m_above($self->m_above + $k);
191             }
192              
193              
194             sub row {
195 7     7 0 3474 my ($self, $i) = @_;
196 7         22 my $N = $self->N;
197 7         162 my $m_below = $self->m_below;
198 7         149 my $m_above = $self->m_above;
199 7         195 my $data = $self->_data;
200              
201 7         48 my $stored_row = $data->[$i];
202 7         15 my $j_eff_max = $m_below + $m_above;
203 7         13 my $row = [];
204 7         19 for (my $j=0;$j<$N;$j++) {
205 49         76 my $j_eff = $j + $m_below - $i;
206 49 100 100     171 push(
207             @$row,
208             ($j_eff < 0 || $j_eff > $j_eff_max)
209             ? 0 : $stored_row->[$j_eff],
210             );
211             }
212              
213 7         26 return $row;
214             }
215              
216              
217             sub column {
218 14     14 0 6839 my ($self, $j) = @_;
219 14         36 my $N = $self->N;
220 14         327 my $m_below = $self->m_below;
221 14         298 my $m_above = $self->m_above;
222 14         274 my $data = $self->_data;
223              
224 14         98 my $j_eff_max = $m_below + $m_above;
225 14         24 my $col = [];
226 14         39 for (my $i=0;$i<$N;$i++) {
227 98         150 my $stored_row = $data->[$i];
228 98         144 my $j_eff = $j + $m_below - $i;
229 98 100 100     347 push(
230             @$col,
231             ($j_eff < 0 || $j_eff > $j_eff_max)
232             ? 0 : $stored_row->[$j_eff],
233             );
234             }
235              
236 14         41 return $col;
237             }
238              
239              
240             sub as_string {
241 0     0 0 0 my ($self) = @_;
242 0         0 my $N = $self->N;
243              
244             return join(
245             qq{\n},
246 0         0 map { join(' ', map { sprintf(q{%7.3f}, $_) } @$_) }
  0         0  
247 0         0 map { $self->row($_) }
  0         0  
248             (0..$N-1),
249             );
250             }
251              
252              
253             sub fill_random {
254 0     0 0 0 my ($self, $min, $max) = @_;
255 0         0 my $N = $self->N;
256 0         0 my $m_below = $self->m_below;
257 0         0 my $m_above = $self->m_above;
258 0         0 my $data = $self->_data;
259              
260 0   0     0 $min //= -1;
261 0   0     0 $max //= 1;
262 0         0 my $range = $max - $min;
263              
264 0         0 for (my $i=0;$i<$N;$i++) {
265 0         0 my $j_min = max(0, $i - $m_below);
266 0         0 my $j_max = min($N - 1, $i + $m_above);
267 0         0 for (my $j=$j_min;$j<=$j_max;$j++) {
268 0         0 $self->element($i, $j, rand($range) + $min);
269             }
270             }
271             }
272              
273              
274             sub multiply_vector {
275 19     19 0 6068 my ($self, $v) = @_;
276 19         47 my $N = $self->N;
277 19         414 my $m_below = $self->m_below;
278 19         398 my $m_above = $self->m_above;
279 19         375 my $data = $self->_data;
280              
281 19         129 my $w = [];
282 19         55 for (my $i=0;$i<$N;$i++) {
283 133         179 my $row = $data->[$i];
284 133         256 my $j_min = max(0, $i - $m_below);
285 133         240 my $j_max = min($N - 1, $i + $m_above);
286 133         199 my $w_i = 0 * $v->[0];
287 133         249 for (my $j=$j_min;$j<=$j_max;$j++) {
288 414         647 my $j_eff = $j + $m_below - $i;
289 414         830 $w_i += $row->[$j_eff] * $v->[$j];
290             }
291 133         304 $w->[$i] = $w_i;
292             }
293              
294 19         91 return $w;
295             }
296              
297              
298             sub multiply_matrix {
299 3     3 0 30 my ($self, $B) = @_;
300 3         11 my $N = $self->N;
301 3         64 my $m_below_A = $self->m_below;
302 3         63 my $m_above_A = $self->m_above;
303 3         61 my $data_A = $self->_data;
304 3         60 my $m_below_B = $B->m_below;
305 3         58 my $m_above_B = $B->m_above;
306 3         59 my $data_B = $B->_data;
307              
308 3 50       26 die "Size mismatch" if ($B->N != $N);
309              
310 3         7 my $m_below = $m_below_A + $m_below_B;
311 3         4 my $m_above = $m_above_A + $m_above_B;
312 3         50 my $C = Math::Matrix::Banded::Square->new(
313             N => $N,
314             m_below => $m_below,
315             m_above => $m_above,
316             );
317              
318 3         83 for (my $i=0;$i<$N;$i++) {
319 21         47 my $row_A = $data_A->[$i];
320 21         51 my $j_min = max(0, $i - $m_below);
321 21         44 my $j_max = min($N - 1, $i + $m_above);
322 21         50 for (my $j=$j_min;$j<=$j_max;$j++) {
323 92         197 my $j_min_A = max(0, $i - $m_below_A);
324 92         174 my $j_max_A = min($N - 1, $i + $m_above_A);
325 92         120 my $c_ij = 0;
326 92         181 for (my $k=$j_min_A;$k<=$j_max_A;$k++) {
327 278         390 my $row_B = $data_B->[$k];
328 278         413 my $j_A = $k + $m_below_A - $i;
329 278         663 my $j_B = $j + $m_below_B - $k;
330 278 100 100     826 next if ($j_B < 0 or $j_B > $#$row_B);
331              
332 168         359 $c_ij += $row_A->[$j_A] * $row_B->[$j_B];
333             }
334 92         183 $C->element($i, $j, $c_ij);
335             }
336             }
337              
338 3         14 return $C;
339             }
340              
341              
342             sub _decompose_LU_without_pivoting {
343 5     5   15 my ($self) = @_;
344 5         15 my $N = $self->N;
345 5         103 my $m_below = $self->m_below;
346 5         104 my $m_above = $self->m_above;
347 5         101 my $data = $self->_data;
348              
349             return try {
350 5     5   345 my $L = Math::Matrix::Banded::Square->new(
351             N => $N,
352             m_below => $m_below,
353             m_above => 0,
354             );
355 5         194 my $U = Math::Matrix::Banded::Square->new(
356             N => $N,
357             m_below => 0,
358             m_above => $m_above,
359             );
360              
361 5         163 my $data_L = $L->_data;
362 5         86 my $data_U = $U->_data;
363 5         14 for (my $i=0;$i<$N;$i++) {
364 35         71 $L->element($i, $i, 1);
365             }
366 5         25 for (my $j=0;$j<$N;$j++) {
367             # NR 2.3.12
368 35         90 my $i_min = max(0, $j - $m_above);
369 35         75 for (my $i=$i_min;$i<=$j;$i++) {
370 74         113 my $j_L0 = $j + $m_below; # this is also j_A
371 74         104 my $j_U0 = $j; # m_below is 0 for U
372 74         155 my $k_min = max(0, $i - $m_below, $j - $m_above);
373 74         126 my $value = $data->[$i]->[$j_L0-$i];
374 74         148 for (my $k=$k_min;$k<$i;$k++) {
375 39         60 my $k_L0 = $k + $m_below;
376 39         142 $value -=
377             $data_L->[$i]->[$k_L0-$i] * $data_U->[$k]->[$j_U0-$k];
378             }
379 74         141 $U->element($i, $j, $value);
380             }
381              
382             # NR 2.3.13
383 35         88 my $i_max = min($N - 1, $j + $m_below);
384 35         84 for (my $i=$j+1;$i<=$i_max;$i++) {
385 50         69 my $j_L0 = $j + $m_below; # this is also j_A
386 50         72 my $j_U0 = $j; # m_below is 0 for U
387 50         129 my $k_min = max(0, $i - $m_below, $j - $m_above);
388 50         89 my $value = $data->[$i]->[$j_L0-$i];
389 50         99 for (my $k=$k_min;$k<$j;$k++) {
390 20         31 my $k_L0 = $k + $m_below;
391 20         59 $value -=
392             $data_L->[$i]->[$k_L0-$i] * $data_U->[$k]->[$j_U0-$k];
393             }
394 50         121 $L->element($i, $j, $value / $data_U->[$j]->[0]);
395             }
396             }
397              
398 5         32 return [$L, $U, [0..$N-1]];
399             }
400             catch {
401 0     0   0 return [undef, undef, undef];
402 5         100 };
403             }
404              
405             sub decompose_LU {
406 4     4 0 900 my ($self) = @_;
407              
408 4         12 my $result = $self->_decompose_LU_without_pivoting;
409 4         178 $self->_set__LU_raw($result);
410 4 50       36 return(defined($result->[0]) ? 1 : undef);
411             }
412              
413              
414             sub solve_LU {
415 4     4 0 14 my ($self, $b) = @_;
416 4         9 my $N = $self->N;
417 4         85 my $L = $self->L;
418 4         87 my $U = $self->U;
419              
420 4 50       33 return undef if (!defined($L));
421              
422 4         62 my $m_below_L = $L->m_below;
423 4         83 my $m_above_L = $L->m_above;
424 4         113 my $data_L = $L->_data;
425 4         81 my $m_below_U = $U->m_below;
426 4         96 my $m_above_U = $U->m_above;
427 4         89 my $data_U = $U->_data;
428              
429             # forward
430 4         26 my $y = [];
431 4         9 my $b_start = 0; # take advantage of leading zeroes in $b
432 4         10 for (my $i=0;$i<$N;$i++) {
433 28         42 my $y_i = $b->[$i];
434 28 100       50 if ($b_start > 0) {
    100          
435 17         36 my $j_min = max(0, $i - $m_below_L, $b_start - 1);
436 17         34 for (my $j=$j_min;$j<$i;$j++) {
437 31         45 my $j_L = $j + $m_below_L - $i;
438 31         70 $y_i -= $data_L->[$i]->[$j_L] * $y->[$j];
439             }
440             }
441             elsif ($y_i != 0) {
442 3         5 $b_start = $i + 1;
443             }
444 28         60 $y->[$i] = $y_i;
445             }
446              
447             # backward
448 4         9 my $x = [];
449 4         10 for (my $i=$N-1;$i>=0;$i--) {
450 28         42 my $x_i = $y->[$i];
451 28         53 my $j_max = min($N - 1, $i + $m_above_U);
452 28         51 for (my $j=$i+1;$j<=$j_max;$j++) {
453 24         38 my $j_U = $j - $i;
454 24         45 $x_i -= $data_U->[$i]->[$j_U] * $x->[$j];
455             }
456 28         63 $x->[$i] = $x_i / $data_U->[$i]->[0];
457             }
458              
459 4         13 return $x;
460             }
461              
462              
463             1;
464              
465             __END__