File Coverage

blib/lib/Math/Symbolic/Custom/CollectSimplify.pm
Criterion Covered Total %
statement 43 44 97.7
branch 10 12 83.3
condition n/a
subroutine 10 10 100.0
pod 0 3 0.0
total 63 69 91.3


line stmt bran cond sub pod time code
1             package Math::Symbolic::Custom::CollectSimplify;
2              
3 2     2   226791 use 5.006001;
  2         6  
4 2     2   10 use strict;
  2         4  
  2         70  
5 2     2   9 use warnings;
  2         5  
  2         127  
6 2     2   8 no warnings 'recursion';
  2         4  
  2         113  
7              
8             =pod
9              
10             =encoding utf8
11              
12             =head1 NAME
13              
14             Math::Symbolic::Custom::CollectSimplify - Simplify Math::Symbolic expressions using Math::Symbolic::Custom::Collect
15              
16             =head1 VERSION
17              
18             Version 0.2
19              
20             =cut
21              
22             our $VERSION = '0.2';
23              
24 2     2   544 use Math::Symbolic qw(:all);
  2         111196  
  2         394  
25 2     2   16 use base 'Math::Symbolic::Custom::Simplification';
  2         4  
  2         1097  
26 2     2   3057 use Math::Symbolic::Custom::Collect 0.21;
  2         25324  
  2         17  
27              
28             =head1 SYNOPSIS
29              
30             use strict;
31             use Math::Symbolic qw(:all);
32             use Math::Symbolic::Custom::CollectSimplify;
33              
34             # 1. We have some expressions
35             my $f1 = parse_from_string('2*(x+3)');
36             my $f2 = parse_from_string('(6*x+2)*(4+x)');
37             my $f3 = parse_from_string('3*x+(2*(x+1))');
38              
39             # 2. Manipulate them in some way to create a big expression
40             my $f4 = $f1 + $f2 + $f3;
41              
42             # 3. We want to simplify this
43             print "Expression: $f4\n";
44             # Expression: ((2 * (x + 3)) + (((6 * x) + 2) * (4 + x))) + ((3 * x) + (2 * (x + 1)))
45              
46             # 4. Try with the simplify() that comes with Math::Symbolic
47             my $f4_s1 = $f4->simplify();
48              
49             print "Original: $f4_s1\n";
50             # Original: (((2 * (3 + x)) + ((2 + (6 * x)) * (4 + x))) + (2 * (1 + x))) + (3 * x)
51              
52             if ( $f4->test_num_equiv($f4_s1) ) {
53             print "\t- Is numerically equivalent with original expression\n";
54             }
55              
56             # 5. Try with the simplify() in this module instead
57             # redefine "simplify()" using the register() method
58             Math::Symbolic::Custom::CollectSimplify->register();
59              
60             my $f4_s2 = $f4->simplify();
61              
62             print "New: $f4_s2\n";
63             # New: (16 + (33 * x)) + (6 * (x ^ 2))
64              
65             if ( $f4->test_num_equiv($f4_s2) ) {
66             print "\t- Is numerically equivalent with original expression\n";
67             }
68              
69             =head1 DESCRIPTION
70              
71             Redefines L's "simplify()" method using the Math::Symbolic module extension class L. This new simplify() method uses
72             "to_collected()" in L.
73              
74             Be aware that "to_collected()" doesn't always produce a simpler expression from the inputted expression, because it does not factorize expressions. Setting the package variable
75             C<$Math::Symbolic::Custom::CollectSimplify::TEST_COMPLEXITY> to 1 will make the simplify() routine check to see if the resultant expression is any simpler (using a measure
76             of expression complexity based on the number of constants, variables and operators) and if not it will return the expression passed to it. Use this if you want to make sure you
77             are getting the simplest possible expression. This behaviour is off by default.
78              
79             =cut
80              
81             our $TEST_COMPLEXITY = 0;
82              
83             sub simplify {
84 3     3 0 174013 my $f1 = shift;
85            
86             # use to_collected() to (potentially) simplify it
87 3         59 my $f2 = $f1->to_collected();
88              
89 3 50       30740 if ( !defined $f2 ) {
90 0         0 return $f1;
91             }
92              
93             # compare on complexity and pass through the input expression
94             # if the collected one is no simpler.
95              
96 3 100       12 if ( $TEST_COMPLEXITY ) {
97              
98 1         5 my $f1_score = test_complexity($f1);
99 1         3 my $f2_score = test_complexity($f2);
100              
101 1 50       9 return $f1_score > $f2_score ? $f2 : $f1;
102             }
103             else {
104 2         22 return $f2;
105             }
106             }
107              
108              
109             # Try to achieve a measure of "complexity" of a Math::Symbolic expression.
110             # The greater the score, the higher the "complexity".
111             sub test_complexity {
112 10     10 0 598072 my ($tree) = @_;
113              
114             # Look at:
115             # 1. the depth of the tree
116             # 2. the number of constants
117             # 3. the number of variable instances (e.g. x * x should count as 2 variables)
118             # 4. the number of operations
119 10         69 my %metrics = ( depth => 0, constants => 0, variables => 0, operations => 0 );
120 10         52 walk($tree, 0, \%metrics);
121              
122 10         32 my $score = 0;
123             # it should be possible to weight these metrics;
124             # for now all metrics are at weight 1.
125 10         49 $score += $_ for values %metrics;
126              
127 10         40 return $score;
128             }
129              
130             # helper routine to walk the Math::Symbolic expression tree and tot up the metrics.
131             sub walk {
132 68     68 0 214 my ($node, $depth, $hr) = @_;
133              
134 68 100       182 $hr->{depth} = $depth if $depth > $hr->{depth};
135              
136 68 100       186 if ($node->term_type() == T_CONSTANT) {
    100          
137 22         102 $hr->{constants}++;
138             } elsif ($node->term_type() == T_VARIABLE) {
139 17         113 $hr->{variables}++;
140             } else {
141 29         172 $hr->{operations}++;
142 29         43 foreach my $child (@{$node->{operands}}) {
  29         129  
143 58         121 walk($child, $depth + 1, $hr);
144             }
145             }
146             }
147              
148             =head1 SEE ALSO
149              
150             L
151              
152             L
153              
154             L
155              
156             =head1 AUTHOR
157              
158             Matt Johnson, C<< >>
159              
160             =head1 ACKNOWLEDGEMENTS
161              
162             Steffen Mueller, author of Math::Symbolic
163              
164             =head1 LICENSE AND COPYRIGHT
165              
166             This software is copyright (c) 2024 by Matt Johnson.
167              
168             This is free software; you can redistribute it and/or modify it under
169             the same terms as the Perl 5 programming language system itself.
170              
171             =cut
172              
173             1;
174             __END__