File Coverage

blib/lib/Math/Symbolic/Custom/DefaultTests.pm
Criterion Covered Total %
statement 170 200 85.0
branch 82 108 75.9
condition 39 60 65.0
subroutine 23 23 100.0
pod 10 10 100.0
total 324 401 80.8


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::Custom::DefaultTests - Default Math::Symbolic tree tests
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic;
11              
12             =head1 DESCRIPTION
13              
14             This is a class of default tests for Math::Symbolic trees. Likewise,
15             Math::Symbolic::Custom::DefaultMods defines default tree transformation
16             routines.
17             For details on how the custom method delegation model works, please have
18             a look at the Math::Symbolic::Custom and Math::Symbolic::Custom::Base
19             classes.
20              
21             =head2 EXPORT
22              
23             Please see the docs for Math::Symbolic::Custom::Base for details, but
24             you should not try to use the standard Exporter semantics with this
25             class.
26              
27             =head1 SUBROUTINES
28              
29             =cut
30              
31             package Math::Symbolic::Custom::DefaultTests;
32              
33 23     23   816 use 5.006;
  23         452  
34 23     23   302 use strict;
  23         67  
  23         767  
35 23     23   98 use warnings;
  23         50  
  23         1487  
36 23     23   14867 use Data::Dumper; # for numerical equivalence test
  23         252774  
  23         2325  
37              
38 23     23   246 no warnings 'recursion';
  23         43  
  23         1636  
39              
40             our $VERSION = '0.613';
41              
42 23     23   12386 use Math::Symbolic::Custom::Base;
  23         75  
  23         1129  
43 23     23   611 BEGIN { *import = \&Math::Symbolic::Custom::Base::aggregate_import }
44              
45 23     23   143 use Math::Symbolic::ExportConstants qw/:all/;
  23         48  
  23         5670  
46              
47 23     23   165 use Carp;
  23         44  
  23         48930  
48              
49             # Class Data: Special variable required by Math::Symbolic::Custom
50             # importing/exporting functionality.
51             # All subroutines that are to be exported to the Math::Symbolic::Custom
52             # namespace should be listed here.
53              
54             our $Aggregate_Export = [
55             qw/
56             is_one
57             is_zero
58             is_zero_or_one
59             is_sum
60             is_constant
61             is_simple_constant
62             is_integer
63             is_identical
64             is_identical_base
65             test_num_equiv
66             /
67             ];
68              
69             =head2 is_zero()
70              
71             Returns true (1) of the tree is a constant and '0'. Returns
72             false (0) otherwise.
73              
74             =cut
75              
76             sub is_zero {
77 36     36 1 78 my $tree = shift;
78 36 100       120 return 0 unless $tree->term_type() == T_CONSTANT;
79 7 100       35 return 1 if $tree->{value} == 0;
80 5         33 return 0;
81             }
82              
83             =head2 is_one()
84              
85             Returns true (1) of the tree is a constant and '1'. Returns
86             false (0) otherwise.
87              
88             =cut
89              
90             sub is_one {
91 35     35 1 1916 my $tree = shift;
92 35 100       103 return 0 unless $tree->term_type() == T_CONSTANT;
93 6 100       35 return 1 if $tree->{value} == 1;
94 1         8 return 0;
95             }
96              
97             =head2 is_zero_or_one()
98              
99             Returns true ('1' for 1, '0E0' for 0) of the tree is a constant and '1' or '0'.
100             Returns false (0) otherwise.
101              
102             =cut
103              
104             sub is_zero_or_one {
105 4     4 1 10 my $tree = shift;
106 4 100       22 return 0 unless $tree->term_type() == T_CONSTANT;
107 2 100       18 return 1 if $tree->{value} == 1;
108 1 50       11 return "0E0" if $tree->{value} == 0;
109 0         0 return 0;
110             }
111              
112             =head2 is_integer()
113              
114             is_integer() returns a boolean.
115              
116             It returns true (1) if the tree is a constant object representing an
117             integer value. It does I compute the value of the tree.
118             (eg. '5*10' is I considered an integer, but '50' is.)
119              
120             It returns false (0) otherwise.
121              
122             =cut
123              
124             sub is_integer {
125 12     12 1 26 my $tree = shift;
126 12 100       51 return 0 unless $tree->term_type() == T_CONSTANT;
127 9         29 my $value = $tree->value();
128 9         66 return ( int($value) == $value );
129             }
130              
131             =head2 is_simple_constant()
132              
133             is_simple_constant() returns a boolean.
134              
135             It returns true if the tree consists of only constants and operators.
136             As opposed to is_constant(), is_simple_constant() does not apply derivatives
137             if necessary.
138              
139             It returns false (0) otherwise.
140              
141             =cut
142              
143             sub is_simple_constant {
144 1095     1095 1 1956 my $tree = shift;
145              
146 1095         1643 my $return = 1;
147             $tree->descend(
148             in_place => 1,
149             before => sub {
150 11372     11372   17926 my $tree = shift;
151 11372         28127 my $ttype = $tree->term_type();
152 11372 100       26630 if ( $ttype == T_CONSTANT ) {
    100          
    50          
153 2293         5278 return undef;
154             }
155             elsif ( $ttype == T_VARIABLE ) {
156 2513         3770 $return = 0;
157 2513         6348 return undef;
158             }
159             elsif ( $ttype == T_OPERATOR ) {
160 6566         16356 return ();
161             }
162             else {
163 0         0 croak "is_simple_constant called on " . "invalid tree type.";
164             }
165             },
166 1095         6927 );
167 1095         6901 return $return;
168             }
169              
170             =head2 is_constant()
171              
172             is_constant() returns a boolean.
173              
174             It returns true (1) if the tree consists of only constants and operators or
175             if it becomes a tree of only constants and operators after application
176             of derivatives.
177              
178             It returns false (0) otherwise.
179              
180             If you need not pay the price of applying derivatives, you should use the
181             is_simple_constant() method instead.
182              
183             =cut
184              
185             sub is_constant {
186 7     7 1 13 my $tree = shift;
187              
188 7         12 my $return = 1;
189             $tree->descend(
190             in_place => 1,
191             before => sub {
192 68     68   91 my $tree = shift;
193 68         162 my $ttype = $tree->term_type();
194 68 100       162 if ( $ttype == T_CONSTANT ) {
    100          
    50          
195 33         63 return undef;
196             }
197             elsif ( $ttype == T_VARIABLE ) {
198 4         7 $return = 0;
199 4         12 return undef;
200             }
201             elsif ( $ttype == T_OPERATOR ) {
202 31         1459 my $tree = $tree->apply_derivatives();
203 31         292 $ttype = $tree->term_type();
204 31 50       68 return undef if $ttype == T_CONSTANT;
205 31 50       62 ( $return = 0 ), return undef
206             if $ttype == T_VARIABLE;
207              
208 31         53 return { descend_into => [ @{ $tree->{operands} } ], };
  31         150  
209             }
210             else {
211 0         0 croak "is_constant called on " . "invalid tree type.";
212             }
213             },
214 7         92 );
215 7         99 return $return;
216             }
217              
218             =head2 is_identical()
219              
220             is_identical() returns a boolean.
221              
222             It compares the tree it is called on to its first argument. If the first
223             argument is not a Math::Symbolic tree, it is sent through the parser.
224              
225             is_identical() returns true (1) if the trees are completely identical. That
226             includes operands of commutating operators having the same order, etc. This
227             does I test of mathematical equivalence! (Which is B harder
228             to test for. If you know how to, I let me know!)
229              
230             It returns false (0) otherwise.
231              
232             =cut
233              
234             sub is_identical {
235 1976     1976 1 6872 my $tree1 = shift;
236 1976         3290 my $tree2 = shift;
237 1976 100       6230 $tree2 = Math::Symbolic::parse_from_string($tree2)
238             if not ref($tree2) =~ /^Math::Symbolic/;
239              
240 1976         7715 my $tt1 = $tree1->term_type();
241 1976         4182 my $tt2 = $tree2->term_type();
242              
243 1976 100       4665 if ( $tt1 != $tt2 ) {
244 571         1789 return 0;
245             }
246             else {
247 1405 100       3879 if ( $tt1 == T_VARIABLE ) {
    100          
    50          
248 331 100       854 return 0 if $tree1->name() ne $tree2->name();
249 319         944 my @sig1 = $tree1->signature();
250 319         773 my @sig2 = $tree2->signature();
251 319 50       814 return 0 if scalar(@sig1) != scalar(@sig2);
252 319         832 for ( my $i = 0 ; $i < @sig1 ; $i++ ) {
253 331 100       1123 return 0 if $sig1[$i] ne $sig2[$i];
254             }
255 318         1120 return 1;
256             }
257             elsif ( $tt1 == T_CONSTANT ) {
258 109         337 my $sp1 = $tree1->special();
259 109         281 my $sp2 = $tree2->special();
260 109 100 33     929 if ( defined $sp1
      66        
      100        
      66        
261             and defined $sp2
262             and $sp1 eq $sp2
263             and $sp1 ne ''
264             and $sp1 =~ /\S/ )
265             {
266 1         4 return 1;
267             }
268 108 100       332 return 1 if $tree1->value() == $tree2->value();
269 9         77 return 0;
270             }
271             elsif ( $tt1 == T_OPERATOR ) {
272 965         2473 my $t1 = $tree1->type();
273 965         1993 my $t2 = $tree2->type();
274 965 100       3325 return 0 if $t1 != $t2;
275             return 0
276 530 50       775 if @{ $tree1->{operands} } != @{ $tree2->{operands} };
  530         1159  
  530         1414  
277              
278 530         960 my $i = 0;
279 530         805 foreach ( @{ $tree1->{operands} } ) {
  530         1266  
280             return 0
281 856 100       2512 unless is_identical( $_, $tree2->{operands}[ $i++ ] );
282             }
283 351         1715 return 1;
284             }
285             else {
286 0         0 croak "is_identical() called on invalid term type.";
287             }
288 0         0 die "Sanity check in is_identical(). Should not be reached.";
289             }
290             }
291              
292             =head2 is_identical_base
293              
294             is_identical_base() returns a boolean.
295              
296             It compares the tree it is called on to its first argument. If the first
297             argument is not a Math::Symbolic tree, it is sent through the parser.
298              
299             is_identical_base() returns true (1) if the trees are identical or
300             if they are exponentiations with the same base. The same gotchas that
301             apply to is_identical apply here, too.
302              
303             For example, 'x*y' and '(x*y)^e' result in a true return value because
304             'x*y' is equal to '(x*y)^1' and this has the same base as '(x*y)^e'.
305              
306             It returns false (0) otherwise.
307              
308             =cut
309              
310             sub is_identical_base {
311 6     6 1 16 my $o1 = shift;
312 6         15 my $o2 = shift;
313 6 50       51 $o2 = Math::Symbolic::parse_from_string($o2)
314             if ref($o2) !~ /^Math::Symbolic/;
315              
316 6         213 my $tt1 = $o1->term_type();
317 6         17 my $tt2 = $o2->term_type();
318              
319 6 100 66     43 my $so1 =
320             ( $tt1 == T_OPERATOR and $o1->type() == B_EXP ) ? $o1->op1() : $o1;
321 6 100 66     28 my $so2 =
322             ( $tt2 == T_OPERATOR and $o2->type() == B_EXP ) ? $o2->op1() : $o2;
323              
324 6         29 return Math::Symbolic::Custom::is_identical( $so1, $so2 );
325             }
326              
327             =head2 is_sum()
328              
329             (beta)
330              
331             is_constant() returns a boolean.
332              
333             It returns true (1) if the tree contains no variables (because it can then
334             be evaluated to a single constant which is a sum). It also returns true if
335             it is a sum or difference of constants and variables. Furthermore, it is
336             true for products of integers and constants because those products are really
337             sums of variables.
338             If none of the above cases match, it applies all derivatives and tries again.
339              
340             It returns false (0) otherwise.
341              
342             Please contact the author in case you encounter bugs in the specs or
343             implementation. The heuristics aren't all that great.
344              
345             =cut
346              
347             sub is_sum {
348 7     7 1 17 my $tree = shift;
349              
350 7         14 my $return = 1;
351             $tree->descend(
352             in_place => 1,
353             before => sub {
354 15     15   27 my $tree = shift;
355 15         80 my $ttype = $tree->term_type();
356              
357 15 100 100     66 if ( $ttype == T_CONSTANT or $ttype == T_VARIABLE ) {
    50          
358 4         15 return undef;
359             }
360             elsif ( $ttype == T_OPERATOR ) {
361 11         45 my $type = $tree->type();
362 11 100 100     1396 if ( $type == B_SUM
    100 100        
    50 33        
    0          
363             or $type == B_DIFFERENCE
364             or $type == U_MINUS )
365             {
366 4         10 return ();
367             }
368             elsif ( $type == B_PRODUCT ) {
369             $return = $tree->{operands}[0]->is_integer()
370 6   66     39 || $tree->{operands}[1]->is_integer();
371 6         23 return undef;
372             }
373             elsif ($type == U_P_DERIVATIVE
374             or $type == U_T_DERIVATIVE )
375             {
376 1         12 my $tree = $tree->apply_derivatives();
377 1         12 $tree = $tree->simplify();
378 1         5 my $ttype = $tree->term_type();
379             return undef
380 1 50 33     4 if ( $ttype == T_CONSTANT
381             or $ttype == T_VARIABLE );
382              
383 1 50       3 if ( $ttype == T_OPERATOR ) {
384 1         2 my $type = $tree->type();
385 1 50 33     4 if ( $type == U_P_DERIVATIVE
386             || $type == U_T_DERIVATIVE )
387             {
388 0         0 $return = 0;
389 0         0 return undef;
390             }
391             else {
392 1         6 return { descend_into => [$tree] };
393             }
394             }
395             else {
396 0         0 die "apply_derivatives "
397             . "screwed the pooch in "
398             . "is_sum().";
399             }
400             }
401             elsif ( is_constant($tree) ) {
402 0         0 return undef;
403             }
404             else {
405 0         0 $return = 0;
406 0         0 return undef;
407             }
408             }
409             else {
410 0         0 croak "is_sum called on invalid tree type.";
411             }
412 0         0 die;
413             },
414 7         79 );
415 7         119 return $return;
416             }
417              
418             =head2 test_num_equiv()
419              
420             Takes another Math::Symbolic tree or a code ref as first
421             argument. Tests the tree
422             it is called on and the one passed in as first argument for
423             equivalence by sampling random numbers for their parameters and
424             evaluating them.
425              
426             This is no guarantee that the functions are actually similar. The
427             computation required for this test may be very high for large
428             numbers of tests.
429              
430             In case of a subroutine reference passed in, the values of the
431             parameters of the Math::Symbolic tree are passed to the sub
432             ref sorted by the parameter names.
433              
434             Following the test-tree, there may be various options as key/value
435             pairs:
436              
437             limits: A hash reference with parameter names as keys and code refs
438             as arguments. A code ref for parameter 'x', will be executed
439             for every number of 'x' that is generated. If the code
440             returns false, the number is discarded and regenerated.
441             tests: The number of tests to carry out. Default: 20
442             epsilon: The accuracy of the numeric comparison. Default: 1e-7
443             retries: The number of attempts to make if a function evaluation
444             throws an error.
445             upper: Upper limit of the random numbers. Default: 10
446             lower: Lower limit of the random numbers. Default: -10
447              
448             =cut
449              
450             sub test_num_equiv {
451 33     33 1 125 my ($t1, $t2) = (shift(), shift());
452 33 50       218 if (ref($t1) !~ /^Math::Symbolic/) {
453 0         0 croak("test_numeric_equivalence() must be called on Math::Symbolic tree");
454             }
455 33 50 66     278 if (ref($t2) !~ /^Math::Symbolic/ and ref($t2) ne 'CODE') {
456 0         0 croak("first argument to test_numeric_equivalence() must be a Math::Symbolic tree or a code reference");
457             }
458              
459 33 100       121 my $is_code = ref($t2) eq 'CODE' ? 1 : 0;
460              
461 33         118 my %args = @_;
462 33   100     170 my $limits = $args{limits} || {};
463 33   50     191 my $tests = $args{tests} || 20;
464 33   50     158 my $eps = $args{epsilon} || 1e-7;
465 33   50     135 my $retries = $args{retries} || 5;
466 33   50     126 my $upper = $args{upper} || 10;
467 33   50     149 my $lower = $args{lower} || -10;
468              
469 33         169 my @s1 = $t1->signature();
470 33 100       122 my @s2 = $is_code ? () : $t2->signature();
471              
472 33         95 my %sig = map {($_=>undef)} @s1, @s2;
  63         176  
473              
474 33         97 my $mult = $upper-$lower;
475              
476 33         64 my $retry = 0;
477 33         119 foreach (1..$tests) {
478 660 50       1811 croak("Could not evaluate test functions with numbers -10..10")
479             if $retry > $retries-1;
480 660         1864 for (keys %sig) {
481 2620         23319 my $num = rand()*$mult - $mult/2;
482 2620 100 100     7892 redo if $limits->{$_} and not $limits->{$_}->($num);
483 940         3953 $sig{$_} = $num;
484             }
485              
486 23     23   210 no warnings;
  23         47  
  23         9456  
487 660         1463 my($y1, $y2);
488 660         14830 eval {$y1 = $t1->value(%sig);};
  660         2624  
489 660 50       1823 if ($@) {
490 0         0 warn "error during evaluation: $@";
491 0         0 $retry++;
492 0         0 $mult /= 2;
493 0         0 redo;
494             }
495 660 100       1732 if ($is_code) {
496 460         949 eval {$y2 = $t2->(map {$sig{$_}} sort keys %sig)};
  460         1756  
  620         6745  
497             }
498             else {
499 200         437 eval {$y2 = $t2->value(%sig);};
  200         689  
500             }
501 660 50       2359 if ($@) {
502 0         0 warn "error during evaluation: $@";
503 0         0 $retry++;
504 0         0 $mult /= 2;
505 0         0 redo;
506             }
507              
508 660 50       1997 if (not defined $y1) {
    50          
509 0         0 warn "Result of '$t1' not defined; ".Dumper(\%sig);
510 0 0       0 next if not defined $y2;
511 0         0 $retry++;
512 0         0 redo;
513             }
514             elsif (not defined $y2) {
515 0         0 warn "Result of '$t2' not defined; ".Dumper(\%sig);
516 0         0 $retry++;
517 0         0 redo;
518             }
519              
520              
521 660 50 33     3045 warn("1: $y1, 2: $y2; ".Dumper(\%sig)), return 0 if $y1+$eps < $y2 or $y1-$eps > $y2;
522              
523 660         1234 $mult = $upper-$lower;
524 660         1753 $retry = 0;
525             }
526              
527 33         412 return 1;
528             }
529              
530             1;
531             __END__