File Coverage

blib/lib/Math/Symbolic/MiscAlgebra.pm
Criterion Covered Total %
statement 68 68 100.0
branch 14 20 70.0
condition n/a
subroutine 11 11 100.0
pod 2 2 100.0
total 95 101 94.0


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::MiscAlgebra - Miscellaneous algebra routines like det()
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic qw/:all/;
11             use Math::Symbolic::MiscAlgebra qw/:all/; # not loaded by Math::Symbolic
12            
13             @matrix = (['x*y', 'z*x', 'y*z'],['x', 'z', 'z'],['x', 'x', 'y']);
14             $det = det @matrix;
15            
16             @vector = ('x', 'y', 'z');
17             $solution = solve_linear(\@matrix, \@vector);
18            
19             =head1 DESCRIPTION
20              
21             This module provides several subroutines related to
22             algebra such as computing the determinant of quadratic matrices, solving
23             linear equation systems and computation of Bell Polynomials.
24              
25             Please note that the code herein may or may not be refactored into
26             the OO-interface of the Math::Symbolic module in the future.
27              
28             =head2 EXPORT
29              
30             None by default.
31              
32             You may choose to have any of the following routines exported to the
33             calling namespace. ':all' tag exports all of the following:
34              
35             det
36             linear_solve
37             bell_polynomial
38              
39             =head1 SUBROUTINES
40              
41             =cut
42              
43             package Math::Symbolic::MiscAlgebra;
44              
45 3     3   2194 use 5.006;
  3         11  
  3         136  
46 3     3   16 use strict;
  3         7  
  3         92  
47 3     3   17 use warnings;
  3         6  
  3         77  
48              
49 3     3   17 use Carp;
  3         8  
  3         237  
50 3     3   20 use Memoize;
  3         7  
  3         210  
51              
52 3     3   18 use Math::Symbolic qw/:all/;
  3         6  
  3         5029  
53              
54             require Exporter;
55             our @ISA = qw(Exporter);
56             our %EXPORT_TAGS = (
57             'all' => [
58             qw(
59             det
60             bell_polynomial
61             linear_solve
62             )
63             ]
64             );
65              
66             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
67              
68             our $VERSION = '0.612';
69              
70             =head2 det
71              
72             det() computes the determinant of a matrix of Math::Symbolic trees (or strings
73             that can be parsed as such). First argument must be a literal array:
74             "det @matrix", where @matrix is an n x n matrix.
75              
76             Please note that calculating determinants of matrices using the
77             straightforward Laplace algorithm is a slow (O(n!))
78             operation. This implementation cannot make use of the various optimizations
79             resulting from the determinant properties since we are dealing with
80             symbolic matrix elements. If you have a matrix of reals, it is strongly
81             suggested that you use Math::MatrixReal or Math::Pari to get the determinant
82             which can be calculated using LR decomposition much faster.
83              
84             On a related note: Calculating the determinant of a 20x20 matrix would take
85             over 77146 years if your Perl could do 1 million calculations per second.
86             Given that we're talking about several method calls per calculation, that's
87             much more than todays computers could do. On the other hand, if you'd be
88             using this straightforward algorithm with numbers only and in C, you might
89             be done in 26 years alright, so please go for the smarter route (better
90             algorithm) instead if you have numbers only.
91              
92             =cut
93              
94             sub det (\@) {
95 7     7 1 1009 my $matrix = shift;
96 7         15 my $size = @$matrix;
97              
98 7         20 foreach (@$matrix) {
99 20 50       118 croak "det(Matrix) requires n x n matrix!" if @$_ != $size;
100 20         36 foreach (@$_) {
101 60 100       462 $_ = Math::Symbolic::parse_from_string($_)
102             if ref($_) !~ /^Math::Symbolic/;
103             }
104             }
105              
106 7 50       63 return $matrix->[0][0] if $size == 1;
107 7 100       125 return $matrix->[0][0] * $matrix->[1][1] - $matrix->[1][0] * $matrix->[0][1]
108             if $size == 2;
109 5         13 return _det_helper( $matrix, $size );
110             }
111              
112             sub _det_helper {
113 9     9   15 my $matrix = shift;
114 9         10 my $size = shift;
115              
116 9 100       142 return $matrix->[0][0] * $matrix->[1][1] * $matrix->[2][2] + $matrix->[1][0]
117             * $matrix->[2][1] * $matrix->[0][2] + $matrix->[2][0] * $matrix->[0][1] *
118             $matrix->[1][2] - $matrix->[0][2] * $matrix->[1][1] * $matrix->[2][0] -
119             $matrix->[1][2] * $matrix->[2][1] * $matrix->[0][0] - $matrix->[2][2] *
120             $matrix->[0][1] * $matrix->[1][0]
121             if $size == 3;
122              
123 1         3 my $det;
124 1         5 foreach ( 0 .. $size - 1 ) {
125 4 100       16 if ( $_ % 2 ) {
126 2         9 $det -=
127             $matrix->[0][$_] *
128             _det_helper( _matrix_slice( $matrix, 0, $_ ), $size - 1 );
129             }
130             else {
131 2         10 $det +=
132             $matrix->[0][$_] *
133             _det_helper( _matrix_slice( $matrix, 0, $_ ), $size - 1 );
134             }
135             }
136 1         7 return $det;
137             }
138              
139             sub _matrix_slice {
140 7     7   2297 my $matrix = shift;
141 7         9 my $x = shift;
142 7         9 my $y = shift;
143              
144 18         34 return [ map { [ @{$_}[ 0 .. $y - 1, $y + 1 ... $#$_ ] ] }
  18         121  
  7         15  
145 7         22 @{$matrix}[ 0 .. $x - 1, $x + 1 .. $#$matrix ] ];
146             }
147              
148             =head2 linear_solve
149              
150             Calculates the solutions x (vector) of a linear equation system of the form
151             C with C being a matrix, C a vector and the solution C a
152             vector. Due to implementation limitations, C must be a quadratic matrix and
153             C must have a dimension that is equivalent to that of C. Furthermore,
154             the determinant of C must be non-zero. The algorithm used is devised from
155             Cramer's Rule and thus inefficient. The preferred algorithm for this task is
156             Gaussian Elimination. If you have a matrix and a vector of real numbers, please
157             consider using either Math::MatrixReal or Math::Pari instead.
158              
159             First argument must be a reference to a matrix (array of arrays) of symbolic
160             terms, second argument must be a reference to a vector (array) of symbolic
161             terms. Strings will be automatically converted to Math::Symbolic trees.
162             Returns a reference to the solution vector.
163              
164             =cut
165              
166             sub linear_solve {
167 1     1 1 3 my ( $m, $v ) = @_;
168 1         2 my $dim = @$v;
169              
170 1 50       5 croak "linear_solve(Matrix, Vector) requires n x n matrix and n-vector!"
171             if @$m != $dim;
172 1         4 foreach (@$m) {
173 3 50       43 croak "linear_solve(Matrix, Vector) requires n x n matrix and n-vector!"
174             if @$_ != $dim;
175 3         9 foreach (@$_) {
176 9 50       140 $_ = Math::Symbolic::parse_from_string($_)
177             if ref($_) !~ /^Math::Symbolic/;
178             }
179             }
180 1         17 foreach (@$v) {
181 3 50       43 $_ = Math::Symbolic::parse_from_string($_)
182             if ref($_) !~ /^Math::Symbolic/;
183             }
184              
185 1         20 my $det = det @$m;
186              
187 1         3 my @vec;
188              
189 1         5 foreach my $i ( 0 .. $#$m ) {
190 3         9 my $nm = _replace_col( $m, $v, $i );
191 3         8 my $det_i = det @$nm;
192 3         10 push @vec, $det_i / $det;
193             }
194              
195 1         9 return \@vec;
196             }
197              
198             sub _replace_col {
199 3     3   4 my $m = shift;
200 3         4 my $v = shift;
201 3         4 my $col = shift;
202 3         5 my $nm = [];
203 3         7 foreach my $i ( 0 .. $#$m ) {
204 9         17 $nm->[$i] = [
205 9         41 @{ $m->[$i] }[ 0 .. $col - 1 ],
206             $v->[$i],
207 9         14 @{ $m->[$i] }[ $col + 1 .. $#$m ]
208             ];
209             }
210 3         7 return $nm;
211             }
212              
213             =head2 bell_polynomial
214              
215             This functions returns the nth Bell Polynomial. It uses memoization for
216             speed increase.
217              
218             First argument is the n. Second (optional) argument is the variable or
219             variable name to use in the polynomial. Defaults to 'x'.
220              
221             The Bell Polynomial is defined as follows:
222              
223             phi_0 (x) = 1
224             phi_n+1(x) = x * ( phi_n(x) + partial_derivative( phi_n(x), x ) )
225              
226             Bell Polynomials are Exponential Polynimals with phi_n(1) = the nth bell
227             number. Please refer to the bell_number() function in the
228             Math::Symbolic::AuxFunctions module for a method of generating these numbers.
229              
230             =cut
231              
232             memoize('bell_polynomial');
233              
234             sub bell_polynomial {
235             my $n = shift;
236             my $var = shift;
237             $var = 'x' if not defined $var;
238             $var = Math::Symbolic::Variable->new($var);
239              
240             return undef if $n < 0;
241             return Math::Symbolic::Constant->new(1) if $n == 0;
242             return $var if $n == 1;
243              
244             my $bell = bell_polynomial( $n - 1 );
245             $bell = Math::Symbolic::Operator->new(
246             '+',
247             Math::Symbolic::Operator->new( '*', $var, $bell )->simplify(),
248             Math::Symbolic::Operator->new(
249             '*',
250             $var,
251             Math::Symbolic::Operator->new( 'partial_derivative', $bell, $var )
252             ->apply_derivatives()->simplify()
253             )->simplify()
254             );
255             return $bell;
256             }
257              
258             1;
259             __END__