File Coverage

blib/lib/Math/Symbolic/Custom/DefaultTests.pm
Criterion Covered Total %
statement 171 201 85.0
branch 82 108 75.9
condition 39 60 65.0
subroutine 23 23 100.0
pod 10 10 100.0
total 325 402 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   376 use 5.006;
  23         87  
  23         852  
34 23     23   121 use strict;
  23         43  
  23         638  
35 23     23   119 use warnings;
  23         46  
  23         666  
36 23     23   40656 use Data::Dumper; # for numerical equivalence test
  23         241173  
  23         2170  
37              
38 23     23   344 no warnings 'recursion';
  23         52  
  23         1312  
39              
40             our $VERSION = '0.612';
41              
42 23     23   13949 use Math::Symbolic::Custom::Base;
  23         65  
  23         915  
43 23     23   471 BEGIN { *import = \&Math::Symbolic::Custom::Base::aggregate_import }
44              
45 23     23   131 use Math::Symbolic::ExportConstants qw/:all/;
  23         50  
  23         6413  
46              
47 23     23   142 use Carp;
  23         50  
  23         54307  
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 66 my $tree = shift;
78 36 100       122 return 0 unless $tree->term_type() == T_CONSTANT;
79 7 100       35 return 1 if $tree->{value} == 0;
80 5         44 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 97 my $tree = shift;
92 35 100       115 return 0 unless $tree->term_type() == T_CONSTANT;
93 6 100       38 return 1 if $tree->{value} == 1;
94 1         7 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 9 my $tree = shift;
106 4 100       26 return 0 unless $tree->term_type() == T_CONSTANT;
107 2 100       12 return 1 if $tree->{value} == 1;
108 1 50       9 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 21 my $tree = shift;
126 12 100       51 return 0 unless $tree->term_type() == T_CONSTANT;
127 9         36 my $value = $tree->value();
128 9         58 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 1088     1088 1 1601 my $tree = shift;
145              
146 1088         1330 my $return = 1;
147             $tree->descend(
148             in_place => 1,
149             before => sub {
150 11343     11343   16561 my $tree = shift;
151 11343         30699 my $ttype = $tree->term_type();
152 11343 100       33975 if ( $ttype == T_CONSTANT ) {
    100          
    50          
153 2285         6640 return undef;
154             }
155             elsif ( $ttype == T_VARIABLE ) {
156 2503         3002 $return = 0;
157 2503         7618 return undef;
158             }
159             elsif ( $ttype == T_OPERATOR ) {
160 6555         18602 return ();
161             }
162             else {
163 0         0 croak "is_simple_constant called on " . "invalid tree type.";
164             }
165             },
166 1088         9379 );
167 1088         7172 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 15 my $tree = shift;
187              
188 7         8 my $return = 1;
189             $tree->descend(
190             in_place => 1,
191             before => sub {
192 68     68   82 my $tree = shift;
193 68         182 my $ttype = $tree->term_type();
194 68 100       216 if ( $ttype == T_CONSTANT ) {
    100          
    50          
195 33         106 return undef;
196             }
197             elsif ( $ttype == T_VARIABLE ) {
198 4         7 $return = 0;
199 4         15 return undef;
200             }
201             elsif ( $ttype == T_OPERATOR ) {
202 31         200 my $tree = $tree->apply_derivatives();
203 31         354 $ttype = $tree->term_type();
204 31 50       81 return undef if $ttype == T_CONSTANT;
205 31 50       62 ( $return = 0 ), return undef
206             if $ttype == T_VARIABLE;
207              
208 31         38 return { descend_into => [ @{ $tree->{operands} } ], };
  31         192  
209             }
210             else {
211 0         0 croak "is_constant called on " . "invalid tree type.";
212             }
213             },
214 7         96 );
215 7         95 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 1966     1966 1 3185 my $tree1 = shift;
236 1966         2533 my $tree2 = shift;
237 1966 100       7861 $tree2 = Math::Symbolic::parse_from_string($tree2)
238             if not ref($tree2) =~ /^Math::Symbolic/;
239              
240 1966         7174 my $tt1 = $tree1->term_type();
241 1966         5560 my $tt2 = $tree2->term_type();
242              
243 1966 100       7395 if ( $tt1 != $tt2 ) {
244 566         2499 return 0;
245             }
246             else {
247 1400 100       4159 if ( $tt1 == T_VARIABLE ) {
    100          
    50          
248 329 100       6944 return 0 if $tree1->name() ne $tree2->name();
249 319         1065 my @sig1 = $tree1->signature();
250 319         2226 my @sig2 = $tree2->signature();
251 319 50       1175 return 0 if scalar(@sig1) != scalar(@sig2);
252 319         930 for ( my $i = 0 ; $i < @sig1 ; $i++ ) {
253 331 100       1280 return 0 if $sig1[$i] ne $sig2[$i];
254             }
255 318         1421 return 1;
256             }
257             elsif ( $tt1 == T_CONSTANT ) {
258 109         403 my $sp1 = $tree1->special();
259 109         321 my $sp2 = $tree2->special();
260 109 100 33     1305 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         6 return 1;
267             }
268 108 100       369 return 1 if $tree1->value() == $tree2->value();
269 9         96 return 0;
270             }
271             elsif ( $tt1 == T_OPERATOR ) {
272 962         2685 my $t1 = $tree1->type();
273 962         2839 my $t2 = $tree2->type();
274 962 100       3878 return 0 if $t1 != $t2;
275 528         1463 return 0
276 528 50       628 if @{ $tree1->{operands} } != @{ $tree2->{operands} };
  528         1394  
277              
278 528         918 my $i = 0;
279 528         664 foreach ( @{ $tree1->{operands} } ) {
  528         1236  
280 854 100       2739 return 0
281             unless is_identical( $_, $tree2->{operands}[ $i++ ] );
282             }
283 351         1983 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 15 my $o1 = shift;
312 6         13 my $o2 = shift;
313 6 50       48 $o2 = Math::Symbolic::parse_from_string($o2)
314             if ref($o2) !~ /^Math::Symbolic/;
315              
316 6         161 my $tt1 = $o1->term_type();
317 6         21 my $tt2 = $o2->term_type();
318              
319 6 100 66     48 my $so1 =
320             ( $tt1 == T_OPERATOR and $o1->type() == B_EXP ) ? $o1->op1() : $o1;
321 6 100 66     39 my $so2 =
322             ( $tt2 == T_OPERATOR and $o2->type() == B_EXP ) ? $o2->op1() : $o2;
323              
324 6         27 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 356 my $tree = shift;
349              
350 7         10 my $return = 1;
351             $tree->descend(
352             in_place => 1,
353             before => sub {
354 15     15   29 my $tree = shift;
355 15         62 my $ttype = $tree->term_type();
356              
357 15 100 100     94 if ( $ttype == T_CONSTANT or $ttype == T_VARIABLE ) {
    50          
358 4         12 return undef;
359             }
360             elsif ( $ttype == T_OPERATOR ) {
361 11         44 my $type = $tree->type();
362 11 100 100     90 if ( $type == B_SUM
    100 100        
    50 33        
    0          
363             or $type == B_DIFFERENCE
364             or $type == U_MINUS )
365             {
366 4         14 return ();
367             }
368             elsif ( $type == B_PRODUCT ) {
369 6   66     42 $return = $tree->{operands}[0]->is_integer()
370             || $tree->{operands}[1]->is_integer();
371 6         22 return undef;
372             }
373             elsif ($type == U_P_DERIVATIVE
374             or $type == U_T_DERIVATIVE )
375             {
376 1         8 my $tree = $tree->apply_derivatives();
377 1         17 $tree = $tree->simplify();
378 1         9 my $ttype = $tree->term_type();
379             return undef
380 1 50 33     8 if ( $ttype == T_CONSTANT
381             or $ttype == T_VARIABLE );
382              
383 1 50       5 if ( $ttype == T_OPERATOR ) {
384 1         3 my $type = $tree->type();
385 1 50 33     9 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         80 );
415 7         116 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 32     32 1 116 my ($t1, $t2) = (shift(), shift());
452 32 50       177 if (ref($t1) !~ /^Math::Symbolic/) {
453 0         0 croak("test_numeric_equivalence() must be called on Math::Symbolic tree");
454             }
455 32 50 66     242 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 32 100       113 my $is_code = ref($t2) eq 'CODE' ? 1 : 0;
460              
461 32         105 my %args = @_;
462 32   100     461 my $limits = $args{limits} || {};
463 32   50     191 my $tests = $args{tests} || 20;
464 32   50     155 my $eps = $args{epsilon} || 1e-7;
465 32   50     136 my $retries = $args{retries} || 5;
466 32   50     128 my $upper = $args{upper} || 10;
467 32   50     128 my $lower = $args{lower} || -10;
468              
469 32         166 my @s1 = $t1->signature();
470 32 100       132 my @s2 = $is_code ? () : $t2->signature();
471              
472 32         66 my %sig = map {($_=>undef)} @s1, @s2;
  59         941  
473              
474 32         75 my $mult = $upper-$lower;
475              
476 32         52 my $retry = 0;
477 32         85 foreach (1..$tests) {
478 640 50       1381 croak("Could not evaluate test functions with numbers -10..10")
479             if $retry > $retries-1;
480 640         1445 for (keys %sig) {
481 2636         14007 my $num = rand()*$mult - $mult/2;
482 2636 100 100     8960 redo if $limits->{$_} and not $limits->{$_}->($num);
483 900         3838 $sig{$_} = $num;
484             }
485              
486 23     23   220 no warnings;
  23         82  
  23         11061  
487 640         1087 my($y1, $y2);
488 640         779 eval {$y1 = $t1->value(%sig);};
  640         2337  
489 640 50       1577 if ($@) {
490 0         0 warn "error during evaluation: $@";
491 0         0 $retry++;
492 0         0 $mult /= 2;
493 0         0 redo;
494             }
495 640 100       1474 if ($is_code) {
496 460         567 eval {$y2 = $t2->(map {$sig{$_}} sort keys %sig)};
  460         1280  
  620         5785  
497             }
498             else {
499 180         224 eval {$y2 = $t2->value(%sig);};
  180         612  
500             }
501 640 50       2083 if ($@) {
502 0         0 warn "error during evaluation: $@";
503 0         0 $retry++;
504 0         0 $mult /= 2;
505 0         0 redo;
506             }
507              
508 640 50       1741 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 640 50 33     3402 warn("1: $y1, 2: $y2; ".Dumper(\%sig)), return 0 if $y1+$eps < $y2 or $y1-$eps > $y2;
522              
523 640         849 $mult = $upper-$lower;
524 640         1205 $retry = 0;
525             }
526              
527 32         380 return 1;
528             }
529              
530             1;
531             __END__