File Coverage

blib/lib/Math/Symbolic/VectorCalculus.pm
Criterion Covered Total %
statement 168 209 80.3
branch 49 88 55.6
condition n/a
subroutine 18 19 94.7
pod 9 9 100.0
total 244 325 75.0


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::VectorCalculus - Symbolically comp. grad, Jacobi matrices etc.
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic qw/:all/;
11             use Math::Symbolic::VectorCalculus; # not loaded by Math::Symbolic
12            
13             @gradient = grad 'x+y*z';
14             # or:
15             $function = parse_from_string('a*b^c');
16             @gradient = grad $function;
17             # or:
18             @signature = qw(x y z);
19             @gradient = grad 'a*x+b*y+c*z', @signature; # Gradient only for x, y, z
20             # or:
21             @gradient = grad $function, @signature;
22            
23             # Similar syntax variations as with the gradient:
24             $divergence = div @functions;
25             $divergence = div @functions, @signature;
26            
27             # Again, similar DWIM syntax variations as with grad:
28             @rotation = rot @functions;
29             @rotation = rot @functions, @signature;
30            
31             # Signatures always inferred from the functions here:
32             @matrix = Jacobi @functions;
33             # $matrix is now array of array references. These hold
34             # Math::Symbolic trees. Or:
35             @matrix = Jacobi @functions, @signature;
36            
37             # Similar to Jacobi:
38             @matrix = Hesse $function;
39             # or:
40             @matrix = Hesse $function, @signature;
41            
42             $wronsky_determinant = WronskyDet @functions, @vars;
43             # or:
44             $wronsky_determinant = WronskyDet @functions; # functions of 1 variable
45            
46             $differential = TotalDifferential $function;
47             $differential = TotalDifferential $function, @signature;
48             $differential = TotalDifferential $function, @signature, @point;
49            
50             $dir_deriv = DirectionalDerivative $function, @vector;
51             $dir_deriv = DirectionalDerivative $function, @vector, @signature;
52            
53             $taylor = TaylorPolyTwoDim $function, $var1, $var2, $degree;
54             $taylor = TaylorPolyTwoDim $function, $var1, $var2,
55             $degree, $var1_0, $var2_0;
56             # example:
57             $taylor = TaylorPolyTwoDim 'sin(x)*cos(y)', 'x', 'y', 2;
58              
59             =head1 DESCRIPTION
60              
61             This module provides several subroutines related to
62             vector calculus such as computing gradients, divergence, rotation,
63             and Jacobi/Hesse Matrices of Math::Symbolic trees.
64             Furthermore it provides means of computing directional derivatives
65             and the total differential of a scalar function and the
66             Wronsky Determinant of a set of n scalar functions.
67              
68             Please note that the code herein may or may not be refactored into
69             the OO-interface of the Math::Symbolic module in the future.
70              
71             =head2 EXPORT
72              
73             None by default.
74              
75             You may choose to have any of the following routines exported to the
76             calling namespace. ':all' tag exports all of the following:
77              
78             grad
79             div
80             rot
81             Jacobi
82             Hesse
83             WronskyDet
84             TotalDifferential
85             DirectionalDerivative
86             TaylorPolyTwoDim
87              
88             =head1 SUBROUTINES
89              
90             =cut
91              
92             package Math::Symbolic::VectorCalculus;
93              
94 2     2   3899 use 5.006;
  2         13  
  2         90  
95 2     2   16 use strict;
  2         4  
  2         77  
96 2     2   11 use warnings;
  2         4  
  2         64  
97              
98 2     2   13 use Carp;
  2         4  
  2         171  
99              
100 2     2   11 use Math::Symbolic qw/:all/;
  2         5  
  2         635  
101 2     2   1459 use Math::Symbolic::MiscAlgebra qw/det/;
  2         7  
  2         6855  
102              
103             require Exporter;
104             our @ISA = qw(Exporter);
105             our %EXPORT_TAGS = (
106             'all' => [
107             qw(
108             grad
109             div
110             rot
111             Jacobi
112             Hesse
113             TotalDifferential
114             DirectionalDerivative
115             TaylorPolyTwoDim
116             WronskyDet
117             )
118             ]
119             );
120              
121             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
122              
123             our $VERSION = '0.612';
124              
125             =begin comment
126              
127             _combined_signature returns the combined signature of unique variable names
128             of all Math::Symbolic trees passed to it.
129              
130             =end comment
131              
132             =cut
133              
134             sub _combined_signature {
135 4     4   13 my %seen = map { ( $_, undef ) } map { ( $_->signature() ) } @_;
  24         44  
  12         38  
136 4         22 return [ sort keys %seen ];
137             }
138              
139             =head2 grad
140              
141             This subroutine computes the gradient of a Math::Symbolic tree representing
142             a function.
143              
144             The gradient of a function f(x1, x2, ..., xn) is defined as the vector:
145              
146             ( df(x1, x2, ..., xn) / d(x1),
147             df(x1, x2, ..., xn) / d(x2),
148             ...,
149             df(x1, x2, ..., xn) / d(xn) )
150              
151             (These are all partial derivatives.) Any good book on calculus will have
152             more details on this.
153              
154             grad uses prototypes to allow for a variety of usages. In its most basic form,
155             it accepts only one argument which may either be a Math::Symbolic tree or a
156             string both of which will be interpreted as the function to compute the
157             gradient for. Optionally, you may specify a second argument which must
158             be a (literal) array of Math::Symbolic::Variable objects or valid
159             Math::Symbolic variable names (strings). These variables will the be used for
160             the gradient instead of the x1, ..., xn inferred from the function signature.
161              
162             =cut
163              
164             sub grad ($;\@) {
165 14     14 1 68 my $original = shift;
166 14 100       87 $original = parse_from_string($original)
167             unless ref($original) =~ /^Math::Symbolic/;
168 14         37 my $signature = shift;
169              
170 14         23 my @funcs;
171 14 100       61 my @signature =
172             ( defined $signature ? @$signature : $original->signature() );
173              
174 14         43 foreach (@signature) {
175 33         121 my $var = Math::Symbolic::Variable->new($_);
176 33         114 my $func = Math::Symbolic::Operator->new(
177             {
178             type => U_P_DERIVATIVE,
179             operands => [ $original->new(), $var ],
180             }
181             );
182 33         113 push @funcs, $func;
183             }
184 14         176 return @funcs;
185             }
186              
187             =head2 div
188              
189             This subroutine computes the divergence of a set of Math::Symbolic trees
190             representing a vectorial function.
191              
192             The divergence of a vectorial function
193             F = (f1(x1, ..., xn), ..., fn(x1, ..., xn)) is defined like follows:
194              
195             sum_from_i=1_to_n( dfi(x1, ..., xn) / dxi )
196              
197             That is, the sum of all partial derivatives of the i-th component function
198             to the i-th coordinate. See your favourite book on calculus for details.
199             Obviously, it is important to keep in mind that the number of function
200             components must be equal to the number of variables/coordinates.
201              
202             Similar to grad, div uses prototypes to offer a comfortable interface.
203             First argument must be a (literal) array of strings and Math::Symbolic trees
204             which represent the vectorial function's components. If no second argument
205             is passed, the variables used for computing the divergence will be
206             inferred from the functions. That means the function signatures will be
207             joined to form a signature for the vectorial function.
208              
209             If the optional second argument is specified, it has to be a (literal)
210             array of Math::Symbolic::Variable objects and valid variable names (strings).
211             These will then be interpreted as the list of variables for computing the
212             divergence.
213              
214             =cut
215              
216             sub div (\@;\@) {
217 9 100       84 my @originals =
218 3         10 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) }
219 3     3 1 35 @{ +shift };
220              
221 3         21 my $signature = shift;
222 3 100       16 $signature = _combined_signature(@originals)
223             if not defined $signature;
224              
225 3 50       13 if ( @$signature != @originals ) {
226 0         0 die "Variable count does not function count for divergence.";
227             }
228              
229 3         9 my @signature = map { Math::Symbolic::Variable->new($_) } @$signature;
  9         31  
230              
231 3         14 my $div = Math::Symbolic::Operator->new(
232             {
233             type => U_P_DERIVATIVE,
234             operands => [ shift(@originals)->new(), shift @signature ],
235             }
236             );
237              
238 3         15 foreach (@originals) {
239 6         20 $div = Math::Symbolic::Operator->new(
240             '+', $div,
241             Math::Symbolic::Operator->new(
242             {
243             type => U_P_DERIVATIVE,
244             operands => [ $_->new(), shift @signature ],
245             }
246             )
247             );
248             }
249 3         20 return $div;
250             }
251              
252             =head2 rot
253              
254             This subroutine computes the rotation of a set of three Math::Symbolic trees
255             representing a vectorial function.
256              
257             The rotation of a vectorial function
258             F = (f1(x1, x2, x3), f2(x1, x2, x3), f3(x1, x2, x3)) is defined as the
259             following vector:
260              
261             ( ( df3/dx2 - df2/dx3 ),
262             ( df1/dx3 - df3/dx1 ),
263             ( df2/dx1 - df1/dx2 ) )
264              
265             Or "nabla x F" for short. Again, I have to refer to the literature for
266             the details on what rotation is. Please note that there have to be
267             exactly three function components and three coordinates because the cross
268             product and hence rotation is only defined in three dimensions.
269              
270             As with the previously introduced subroutines div and grad, rot
271             offers a prototyped interface.
272             First argument must be a (literal) array of strings and Math::Symbolic trees
273             which represent the vectorial function's components. If no second argument
274             is passed, the variables used for computing the rotation will be
275             inferred from the functions. That means the function signatures will be
276             joined to form a signature for the vectorial function.
277              
278             If the optional second argument is specified, it has to be a (literal)
279             array of Math::Symbolic::Variable objects and valid variable names (strings).
280             These will then be interpreted as the list of variables for computing the
281             rotation. (And please excuse my copying the last two paragraphs from above.)
282              
283             =cut
284              
285             sub rot (\@;\@) {
286 1     1 1 3 my $originals = shift;
287 3 50       48 my @originals =
288 1         3 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) }
289             @$originals;
290              
291 1         20 my $signature = shift;
292 1 50       6 $signature = _combined_signature(@originals)
293             unless defined $signature;
294              
295 1 50       5 if ( @originals != 3 ) {
296 0         0 die "Rotation only defined for functions of three components.";
297             }
298 1 50       4 if ( @$signature != 3 ) {
299 0         0 die "Rotation only defined for three variables.";
300             }
301              
302             return (
303 1         6 Math::Symbolic::Operator->new(
304             '-',
305             Math::Symbolic::Operator->new(
306             {
307             type => U_P_DERIVATIVE,
308             operands => [ $originals[2]->new(), $signature->[1] ],
309             }
310             ),
311             Math::Symbolic::Operator->new(
312             {
313             type => U_P_DERIVATIVE,
314             operands => [ $originals[1]->new(), $signature->[2] ],
315             }
316             )
317             ),
318             Math::Symbolic::Operator->new(
319             '-',
320             Math::Symbolic::Operator->new(
321             {
322             type => U_P_DERIVATIVE,
323             operands => [ $originals[0]->new(), $signature->[2] ],
324             }
325             ),
326             Math::Symbolic::Operator->new(
327             {
328             type => U_P_DERIVATIVE,
329             operands => [ $originals[2]->new(), $signature->[0] ],
330             }
331             )
332             ),
333             Math::Symbolic::Operator->new(
334             '-',
335             Math::Symbolic::Operator->new(
336             {
337             type => U_P_DERIVATIVE,
338             operands => [ $originals[1]->new(), $signature->[0] ],
339             }
340             ),
341             Math::Symbolic::Operator->new(
342             {
343             type => U_P_DERIVATIVE,
344             operands => [ $originals[0]->new(), $signature->[1] ],
345             }
346             )
347             )
348             );
349             }
350              
351             =head2 Jacobi
352              
353             Jacobi() returns the Jacobi matrix of a given vectorial function.
354             It expects any number of arguments (strings and/or Math::Symbolic trees)
355             which will be interpreted as the vectorial function's components.
356             Variables used for computing the matrix are, by default, inferred from the
357             combined signature of the components. By specifying a second literal
358             array of variable names as (second) argument, you may override this
359             behaviour.
360              
361             The Jacobi matrix is the vector of gradient vectors of the vectorial
362             function's components.
363              
364             =cut
365              
366             sub Jacobi (\@;\@) {
367 5 100       63 my @funcs =
368 2         6 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) }
369 2     2 1 5 @{ +shift() };
370              
371 2         21 my $signature = shift;
372 2 50       32 my @signature = (
373             defined $signature
374             ? (
375             map {
376 1         4 ( ref($_) =~ /^Math::Symbolic/ )
377             ? $_
378             : parse_from_string($_)
379             } @$signature
380             )
381 2 100       7 : ( @{ +_combined_signature(@funcs) } )
382             );
383              
384 2         28 return map { [ grad $_, @signature ] } @funcs;
  5         17  
385             }
386              
387             =head2 Hesse
388              
389             Hesse() returns the Hesse matrix of a given scalar function. First
390             argument must be a string (to be parsed as a Math::Symbolic tree)
391             or a Math::Symbolic tree. As with Jacobi(), Hesse() optionally
392             accepts an array of signature variables as second argument.
393              
394             The Hesse matrix is the Jacobi matrix of the gradient of a scalar function.
395              
396             =cut
397              
398             sub Hesse ($;\@) {
399 1     1 1 3 my $function = shift;
400 1 50       10 $function = parse_from_string($function)
401             unless ref($function) =~ /^Math::Symbolic/;
402 1         22 my $signature = shift;
403 0 0       0 my @signature = (
404             defined $signature
405             ? (
406             map {
407 1 50       10 ( ref($_) =~ /^Math::Symbolic/ )
408             ? $_
409             : parse_from_string($_)
410             } @$signature
411             )
412             : $function->signature()
413             );
414              
415 1         7 my @gradient = grad $function, @signature;
416 1         7 return Jacobi @gradient, @signature;
417             }
418              
419             =head2 TotalDifferential
420              
421             This function computes the total differential of a scalar function of
422             multiple variables in a certain point.
423              
424             First argument must be the function to derive. The second argument is
425             an optional (literal) array of variable names (strings) and
426             Math::Symbolic::Variable objects to be used for deriving. If the argument
427             is not specified, the functions signature will be used. The third argument
428             is also an optional array and denotes the set of variable (names) to use for
429             indicating the point for which to evaluate the differential. It must have
430             the same number of elements as the second argument.
431             If not specified the variable names used as coordinated (the second argument)
432             with an appended '_0' will be used as the point's components.
433              
434             =cut
435              
436             sub TotalDifferential ($;\@\@) {
437 3     3 1 7 my $function = shift;
438 3 50       28 $function = parse_from_string($function)
439             unless ref($function) =~ /^Math::Symbolic/;
440              
441 3         72 my $sig = shift;
442 3 100       16 $sig = [ $function->signature() ] if not defined $sig;
443 3         10 my @sig = map { Math::Symbolic::Variable->new($_) } @$sig;
  6         26  
444              
445 3         8 my $point = shift;
446 3 100       14 $point = [ map { $_->name() . '_0' } @sig ] if not defined $point;
  4         14  
447 3         11 my @point = map { Math::Symbolic::Variable->new($_) } @$point;
  6         24  
448              
449 3 50       13 if ( @point != @sig ) {
450 0         0 croak "Signature dimension does not match point dimension.";
451             }
452              
453 3         21 my @grad = grad $function, @sig;
454 3 50       11 if ( @grad != @sig ) {
455 0         0 croak "Signature dimension does not match function grad dim.";
456             }
457              
458 3         8 foreach (@grad) {
459 6         14 my @point_copy = @point;
460 6         10 $_->implement( map { ( $_->name() => shift(@point_copy) ) } @sig );
  12         39  
461             }
462              
463 3         19 my $d =
464             Math::Symbolic::Operator->new( '*', shift(@grad),
465             Math::Symbolic::Operator->new( '-', shift(@sig), shift(@point) ) );
466              
467 3         23 $d +=
468             Math::Symbolic::Operator->new( '*', shift(@grad),
469             Math::Symbolic::Operator->new( '-', shift(@sig), shift(@point) ) )
470             while @grad;
471              
472 3         30 return $d;
473             }
474              
475             =head2 DirectionalDerivative
476              
477             DirectionalDerivative computes the directional derivative of a scalar function
478             in the direction of a specified vector. With f being the function and X, A being
479             vectors, it looks like this: (this is a partial derivative)
480              
481             df(X)/dA = grad(f(X)) * (A / |A|)
482              
483             First argument must be the function to derive (either a string or a valid
484             Math::Symbolic tree). Second argument must be vector into whose direction to
485             derive. It is to be specified as an array of variable names and objects.
486             Third argument is the optional signature to be used for computing the gradient.
487             Please see the documentation of the grad function for details. It's
488             dimension must match that of the directional vector.
489              
490             =cut
491              
492             sub DirectionalDerivative ($\@;\@) {
493 2     2 1 13 my $function = shift;
494 2 50       17 $function = parse_from_string($function)
495             unless ref($function) =~ /^Math::Symbolic/;
496              
497 2         49 my $vec = shift;
498 2         7 my @vec = map { Math::Symbolic::Variable->new($_) } @$vec;
  5         19  
499              
500 2         5 my $sig = shift;
501 2 100       13 $sig = [ $function->signature() ] if not defined $sig;
502 2         8 my @sig = map { Math::Symbolic::Variable->new($_) } @$sig;
  5         17  
503              
504 2 50       10 if ( @vec != @sig ) {
505 0         0 croak "Signature dimension does not match vector dimension.";
506             }
507              
508 2         10 my @grad = grad $function, @sig;
509 2 50       11 if ( @grad != @sig ) {
510 0         0 croak "Signature dimension does not match function grad dim.";
511             }
512              
513 2         18 my $two = Math::Symbolic::Constant->new(2);
514 5         19 my @squares =
515 2         8 map { Math::Symbolic::Operator->new( '^', $_, $two ) } @vec;
516              
517 2         5 my $abs_vec = shift @squares;
518 2         20 $abs_vec += shift(@squares) while @squares;
519              
520 2         12 $abs_vec =
521             Math::Symbolic::Operator->new( '^', $abs_vec,
522             Math::Symbolic::Constant->new( 1 / 2 ) );
523              
524 2         6 @vec = map { $_ / $abs_vec } @vec;
  5         18  
525              
526 2         12 my $dd = Math::Symbolic::Operator->new( '*', shift(@grad), shift(@vec) );
527              
528 2         17 $dd += Math::Symbolic::Operator->new( '*', shift(@grad), shift(@vec) )
529             while @grad;
530              
531 2         28 return $dd;
532             }
533              
534             =begin comment
535              
536             This computes the taylor binomial
537              
538             (d/dx*(x-x0)+d/dy*(y-y0))^n * f(x0, y0)
539              
540             =end comment
541              
542             =cut
543              
544             sub _taylor_binomial {
545 1     1   3 my $f = shift;
546 1         3 my $a = shift;
547 1         2 my $b = shift;
548 1         2 my $a0 = shift;
549 1         2 my $b0 = shift;
550 1         2 my $n = shift;
551              
552 1         5 $f = $f->new();
553 1         5 my $da = $a - $a0;
554 1         4 my $db = $b - $b0;
555              
556 1         6 $f->implement( $a->name() => $a0, $b->name() => $b0 );
557              
558 1 50       10 return Math::Symbolic::Constant->one() if $n == 0;
559 1 50       10 return $da *
560             Math::Symbolic::Operator->new( 'partial_derivative', $f->new(), $a0 ) +
561             $db *
562             Math::Symbolic::Operator->new( 'partial_derivative', $f->new(), $b0 )
563             if $n == 1;
564              
565 0         0 my $n_obj = Math::Symbolic::Constant->new($n);
566              
567 0         0 my $p_a_deriv = $f->new();
568             $p_a_deriv =
569             Math::Symbolic::Operator->new( 'partial_derivative', $p_a_deriv, $a0 )
570 0         0 for 1 .. $n;
571              
572 0         0 my $res =
573             Math::Symbolic::Operator->new( '*', $p_a_deriv,
574             Math::Symbolic::Operator->new( '^', $da, $n_obj ) );
575              
576 0         0 foreach my $k ( 1 .. $n - 1 ) {
577 0         0 $p_a_deriv = $p_a_deriv->op1()->new();
578              
579 0         0 my $deriv = $p_a_deriv;
580             $deriv =
581             Math::Symbolic::Operator->new( 'partial_derivative', $deriv, $b0 )
582 0         0 for 1 .. $k;
583              
584 0         0 my $k_obj = Math::Symbolic::Constant->new($k);
585 0         0 $res += Math::Symbolic::Operator->new(
586             '*',
587             Math::Symbolic::Constant->new( _over( $n, $k ) ),
588             Math::Symbolic::Operator->new(
589             '*', $deriv,
590             Math::Symbolic::Operator->new(
591             '*',
592             Math::Symbolic::Operator->new(
593             '^', $da, Math::Symbolic::Constant->new( $n - $k )
594             ),
595             Math::Symbolic::Operator->new( '^', $db, $k_obj )
596             )
597             )
598             );
599             }
600              
601 0         0 my $p_b_deriv = $f->new();
602             $p_b_deriv =
603             Math::Symbolic::Operator->new( 'partial_derivative', $p_b_deriv, $b0 )
604 0         0 for 1 .. $n;
605              
606 0         0 $res +=
607             Math::Symbolic::Operator->new( '*', $p_b_deriv,
608             Math::Symbolic::Operator->new( '^', $db, $n_obj ) );
609              
610 0         0 return $res;
611             }
612              
613             =begin comment
614              
615             This computes
616              
617             / n \
618             | |
619             \ k /
620              
621             =end comment
622              
623             =cut
624              
625             sub _over {
626 0     0   0 my $n = shift;
627 0         0 my $k = shift;
628              
629 0 0       0 return 1 if $k == 0;
630 0 0       0 return _over( $n, $n - $k ) if $k > $n / 2;
631              
632 0         0 my $prod = 1;
633 0         0 my $i = $n;
634 0         0 my $j = $k;
635 0         0 while ( $i > $k ) {
636 0         0 $prod *= $i;
637 0 0       0 $prod /= $j if $j > 1;
638 0         0 $i--;
639 0         0 $j--;
640             }
641              
642 0         0 return ($prod);
643             }
644              
645             =begin comment
646              
647             _faculty() computes the product that is the faculty of the
648             first argument.
649              
650             =end comment
651              
652             =cut
653              
654             sub _faculty {
655 1     1   2 my $num = shift;
656 1 50       6 croak "Cannot calculate faculty of negative numbers."
657             if $num < 0;
658 1         9 my $fac = Math::Symbolic::Constant->one();
659 1 50       9 return $fac if $num <= 1;
660 0         0 for ( my $i = 2 ; $i <= $num ; $i++ ) {
661 0         0 $fac *= Math::Symbolic::Constant->new($i);
662             }
663 0         0 return $fac;
664             }
665              
666             =head2 TaylorPolyTwoDim
667              
668             This subroutine computes the Taylor Polynomial for functions of two
669             variables. Please refer to the documentation of the TaylorPolynomial
670             function in the Math::Symbolic::MiscCalculus package for an explanation
671             of single dimensional Taylor Polynomials. This is the counterpart in
672             two dimensions.
673              
674             First argument must be the function to approximate with the Taylor Polynomial
675             either as a string or a Math::Symbolic tree. Second and third argument
676             must be the names of the two coordinates. (These may alternatively be
677             Math::Symbolic::Variable objects.) Fourth argument must be
678             the degree of the Taylor Polynomial. Fifth and Sixth arguments are optional
679             and specify the names of the variables to introduce as the point of
680             approximation. These default to the names of the coordinates with '_0'
681             appended.
682              
683             =cut
684              
685             sub TaylorPolyTwoDim ($$$$;$$) {
686 2     2 1 7 my $function = shift;
687 2 50       19 $function = parse_from_string($function)
688             unless ref($function) =~ /^Math::Symbolic/;
689              
690 2         43 my $x1 = shift;
691 2 50       15 $x1 = Math::Symbolic::Variable->new($x1)
692             unless ref($x1) eq 'Math::Symbolic::Variable';
693 2         6 my $x2 = shift;
694 2 50       12 $x2 = Math::Symbolic::Variable->new($x2)
695             unless ref($x2) eq 'Math::Symbolic::Variable';
696              
697 2         4 my $n = shift;
698              
699 2         4 my $x1_0 = shift;
700 2 50       12 $x1_0 = $x1->name() . '_0' if not defined $x1_0;
701 2 50       13 $x1_0 = Math::Symbolic::Variable->new($x1_0)
702             unless ref($x1_0) eq 'Math::Symbolic::Variable';
703              
704 2         3 my $x2_0 = shift;
705 2 50       11 $x2_0 = $x2->name() . '_0' if not defined $x2_0;
706 2 50       12 $x2_0 = Math::Symbolic::Variable->new($x2_0)
707             unless ref($x2_0) eq 'Math::Symbolic::Variable';
708              
709 2         7 my $x1_n = $x1->name();
710 2         7 my $x2_n = $x2->name();
711              
712 2         15 my $dx1 = $x1 - $x1_0;
713 2         6 my $dx2 = $x2 - $x2_0;
714              
715 2         8 my $copy = $function->new();
716 2         13 $copy->implement( $x1_n => $x1_0, $x2_n => $x2_0 );
717              
718 2         14 my $taylor = $copy;
719              
720 2 100       16 return $taylor if $n == 0;
721              
722 1         4 foreach my $k ( 1 .. $n ) {
723 1         6 $taylor +=
724             Math::Symbolic::Operator->new( '/',
725             _taylor_binomial( $function->new(), $x1, $x2, $x1_0, $x2_0, $k ),
726             _faculty($k) );
727             }
728              
729 1         10 return $taylor;
730             }
731              
732             =head2 WronskyDet
733              
734             WronskyDet() computes the Wronsky Determinant of a set of n functions.
735              
736             First argument is required and a (literal) array of n functions. Second
737             argument is optional and a (literal) array of n variables or variable names.
738             If the second argument is omitted, the variables used for deriving are inferred
739             from function signatures. This requires, however, that the function signatures
740             have exactly one element. (And the function this exactly one variable.)
741              
742             =cut
743              
744             sub WronskyDet (\@;\@) {
745 1     1 1 3 my $functions = shift;
746 2 50       40 my @functions =
747 1         4 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) }
748             @$functions;
749 1         22 my $vars = shift;
750 1 50       9 my @vars = ( defined $vars ? @$vars : () );
751 0         0 @vars = map {
752 1 50       4 my @sig = $_->signature();
753 0 0       0 croak "Cannot infer function signature for WronskyDet."
754             if @sig != 1;
755 0         0 shift @sig;
756             } @functions if not defined $vars;
757 1         3 @vars = map { Math::Symbolic::Variable->new($_) } @vars;
  2         11  
758 1 50       5 croak "Number of vars doesn't match num of functions in WronskyDet."
759             if not @vars == @functions;
760              
761 1         3 my @matrix;
762 1         3 push @matrix, [@functions];
763 1         4 foreach ( 2 .. @functions ) {
764 1         2 my $i = 0;
765 2         11 @functions = map {
766 1         4 Math::Symbolic::Operator->new( 'partial_derivative', $_,
767             $vars[ $i++ ] )
768             } @functions;
769 1         6 push @matrix, [@functions];
770             }
771 1         7 return det @matrix;
772             }
773              
774             1;
775             __END__