File Coverage

blib/lib/Math/Symbolic/Custom/Polynomial.pm
Criterion Covered Total %
statement 217 222 97.7
branch 70 100 70.0
condition 17 21 80.9
subroutine 12 12 100.0
pod 0 4 0.0
total 316 359 88.0


line stmt bran cond sub pod time code
1             package Math::Symbolic::Custom::Polynomial;
2              
3 2     2   296035 use 5.006;
  2         11  
4 2     2   11 use strict;
  2         5  
  2         78  
5 2     2   9 use warnings;
  2         3  
  2         293  
6              
7             =pod
8              
9             =encoding utf8
10              
11             =head1 NAME
12              
13             Math::Symbolic::Custom::Polynomial - Polynomial routines for Math::Symbolic
14              
15             =head1 VERSION
16              
17             Version 0.31
18              
19             =cut
20              
21             require Exporter;
22              
23             our @ISA = qw(Exporter);
24             our @EXPORT = qw/symbolic_poly/;
25              
26             our $VERSION = '0.31';
27              
28 2     2   459 use Math::Symbolic 0.613 qw(:all);
  2         118663  
  2         579  
29 2     2   1850 use Math::Symbolic::Custom::Collect 0.36;
  2         36624  
  2         19  
30 2     2   197 use Math::Symbolic::Custom::Base;
  2         3  
  2         82  
31              
32             BEGIN {
33 2     2   117 *import = \&Math::Symbolic::Custom::Base::aggregate_import
34             }
35            
36             our $Aggregate_Export = [qw/test_polynomial apply_synthetic_division apply_polynomial_division/];
37              
38             # attempt to circumvent above redefinition of import function
39             Math::Symbolic::Custom::Polynomial->export_to_level(1, undef, 'symbolic_poly');
40              
41 2     2   13 use Carp;
  2         4  
  2         6521  
42              
43             =head1 DESCRIPTION
44              
45             This is the beginnings of a module to provide some polynomial utility routines for Math::Symbolic.
46              
47             "symbolic_poly()" creates a polynomial Math::Symbolic expression according to the supplied variable and
48             coefficients, and "test_polynomial()" attempts the inverse, it looks at a Math::Symbolic expression and
49             tries to extract polynomial coefficients (so long as the expression represents a polynomial). The
50             "apply_synthetic_division()" and "apply_polynomial_division()" methods will attempt to perform division
51             on a target polynomial.
52              
53             =head1 EXAMPLE
54              
55             use strict;
56             use Math::Symbolic qw(:all);
57             use Math::Symbolic::Custom::Polynomial;
58             use Math::Complex;
59              
60             # create a polynomial expression
61             my $f1 = symbolic_poly('x', [5, 4, 3, 2, 1]);
62             print "Output: $f1\n\n\n";
63             # Output: ((((5 * (x ^ 4)) + (4 * (x ^ 3))) + (3 * (x ^ 2))) + (2 * x)) + 1
64              
65             # also works with symbols
66             my $f2 = symbolic_poly('t', ['a/2', 'u', 0]);
67             print "Output: $f2\n\n\n";
68             # Output: ((a / 2) * (t ^ 2)) + (u * t)
69              
70             # analyze a polynomial with complex roots
71             my $complex_poly = parse_from_string("y^2 + y + 1");
72             my ($var, $coeffs, $disc, $roots) = $complex_poly->test_polynomial('y');
73              
74             my $degree = scalar(@{$coeffs})-1;
75             print "'$complex_poly' is a polynomial in $var of degree $degree with " .
76             "coefficients (ordered in descending powers): (", join(", ", @{$coeffs}), ")\n";
77             print "The discriminant has: $disc\n";
78             print "Expressions for the roots are:\n\t$roots->[0]\n\t$roots->[1]\n";
79              
80             # evaluate the root expressions as they should resolve to numbers
81             # 'i' => i glues Math::Complex and Math::Symbolic
82             my $root1 = $roots->[0]->value('i' => i);
83             my $root2 = $roots->[1]->value('i' => i);
84             # $root1 and $root2 are Math::Complex numbers
85             print "The roots evaluate to: (", $root1, ", ", $root2, ")\n";
86              
87             # plug back in to verify the roots take the poly back to zero
88             # (or at least, as numerically close as can be gotten).
89             print "Putting back into original polynomial:-\n\tat y = $root1:\t",
90             $complex_poly->value('y' => $root1),
91             "\n\tat y = $root2:\t",
92             $complex_poly->value('y' => $root2), "\n\n\n";
93              
94             # analyze a polynomial with a parameter
95             my $some_poly = parse_from_string("x^2 + 2*k*x + (k^2 - 4)");
96             ($var, $coeffs, $disc, $roots) = $some_poly->test_polynomial('x');
97              
98             $degree = scalar(@{$coeffs})-1;
99             print "'$some_poly' is a polynomial in $var of degree $degree with " .
100             "coefficients (ordered in descending powers): (", join(", ", @{$coeffs}), ")\n";
101             print "The discriminant has: $disc\n";
102             print "Expressions for the roots are:\n\t$roots->[0]\n\t$roots->[1]\n";
103              
104             # evaluate the root expressions for k = 3 (for example)
105             my $root1 = $roots->[0]->value('k' => 3);
106             my $root2 = $roots->[1]->value('k' => 3);
107             print "Evaluating at k = 3, roots are: (", $root1, ", ", $root2, ")\n";
108              
109             # plug back in to verify
110             print "Putting back into original polynomial:-\n\tat k = 3 and x = $root1:\t",
111             $some_poly->value('k' => 3, 'x' => $root1),
112             "\n\tat k = 3 and x = $root2:\t",
113             $some_poly->value('k' => 3, 'x' => $root2), "\n\n";
114              
115             =head1 symbolic_poly()
116              
117             Exported by default (or it should be; try calling it directly if that fails). Constructs a Math::Symbolic
118             expression corresponding to the passed parameters: a symbol for the desired indeterminate variable, and an array
119             ref to the coefficients in descending order (which can also be symbols).
120              
121             See the 'Example' section above for examples of use.
122              
123             =cut
124              
125             sub symbolic_poly {
126 169     169 0 3697486 my ($var, $coeffs) = @_;
127              
128 169 50       1010 return undef unless defined $var;
129 169 50 33     1457 return undef unless defined($coeffs) and (ref($coeffs) eq 'ARRAY');
130              
131 169         382 my @c = reverse @{ $coeffs }; # reverse the coefficient array as it will be built in ascending order
  169         622  
132 169 50       672 return undef unless scalar(@c) > 0; # TODO: perhaps should return 0?
133              
134 169 100       645 if ( scalar(@c) == 1 ) {
135 25         69 my $single = $c[0];
136 25 100       195 $single = parse_from_string($single) unless ref($single) =~ /^Math::Symbolic/;
137 25         38170 return $single;
138             }
139              
140 144 50       1148 $var = parse_from_string($var) unless ref($var) =~ /^Math::Symbolic/;
141 144 50       725499 return undef unless defined $var;
142              
143 144         366 my @to_sum;
144 144         374 my $exp = 0;
145              
146 144         607 BUILD_POLY: while ( defined(my $co = shift @c) ) {
147              
148 497 50       1912 if ( length($co) > 0 ) {
149 497 100       7228 $co = parse_from_string($co) unless ref($co) =~ /^Math::Symbolic/;
150              
151 497 100 100     746019 if ( ($co->term_type() == T_CONSTANT) && ($co->value() == 0) ) {
152 34         535 $exp++;
153 34         202 next BUILD_POLY;
154             }
155              
156 463 100       6454 if ( $exp == 0 ) {
    100          
157 135         364 push @to_sum, $co;
158             }
159             elsif ( $exp == 1 ) {
160 127 100 100     425 if ( ($co->term_type() == T_CONSTANT) && ($co->value() == 1) ) {
161 18         261 push @to_sum, $var;
162             }
163             else {
164 109         2614 push @to_sum, Math::Symbolic::Operator->new('*', $co, $var);
165             }
166             }
167             else {
168 201 100 100     774 if ( ($co->term_type() == T_CONSTANT) && ($co->value() == 1) ) {
169 39         674 push @to_sum, Math::Symbolic::Operator->new('^', $var, $exp);
170             }
171             else {
172 162         2018 push @to_sum, Math::Symbolic::Operator->new('*', $co, Math::Symbolic::Operator->new('^', $var, $exp));
173             }
174             }
175             }
176              
177 463         414587 $exp++;
178             }
179              
180 144         392 @to_sum = reverse @to_sum; # restore descending order
181 144         330 my $nt = shift @to_sum;
182 144         420 while (@to_sum) {
183 324         5718 my $e = shift @to_sum;
184 324         1076 $nt = Math::Symbolic::Operator->new( '+', $nt, $e );
185             }
186              
187 144         3867 return $nt;
188             }
189              
190             =head1 method test_polynomial()
191              
192             Exported through the Math::Symbolic module extension class. Call it on a polynomial Math::Symbolic expression and it will
193             try to determine the coefficient expressions.
194              
195             Takes one parameter, the indeterminate variable. If this is not provided, test_polynomial will try to detect the variable. This
196             can be useful to test if an arbitrary Math::Symbolic expression looks like a polynomial.
197            
198             If the expression looks like a polynomial of degree 2, then it will apply the quadratic equation to produce
199             expressions for the roots, and the discriminant.
200              
201             Example (also see 'Example' section above):
202              
203             use strict;
204             use Math::Symbolic qw(:all);
205             use Math::Symbolic::Custom::Polynomial;
206             use Math::Complex;
207              
208             # finding roots with Math::Polynomial::Solve
209             use Math::Polynomial::Solve qw(poly_roots coefficients);
210             coefficients order => 'descending';
211              
212             # some big polynomial
213             my $big_poly = parse_from_string("phi^8 + 3*phi^7 - 5*phi^6 + 2*phi^5 -7*phi^4 + phi^3 + phi^2 - 2*phi + 9");
214             # if test_polynomial() is not supplied with the indeterminate variable, it will try to autodetect
215             my ($var, $co) = $big_poly->test_polynomial();
216             my @coeffs = @{$co};
217             my $degree = scalar(@coeffs)-1;
218              
219             print "'$big_poly' is a polynomial in $var of degree $degree with " .
220             "coefficients (ordered in descending powers): (", join(", ", @coeffs), ")\n";
221              
222             # Find the roots of the polynomial using Math::Polynomial::Solve.
223             my @roots = poly_roots(
224             # call value() on each coefficient to get a number.
225             # if there were any parameters, we would have to supply their value
226             # here to force the coefficients down to a number.
227             map { $_->value() } @coeffs
228             );
229              
230             print "The roots and corresponding values of the polynomial are:-\n";
231             foreach my $root (@roots) {
232             # put back into the original expression to verify
233             my $val = $big_poly->value('phi' => $root);
234             print "\t$root => $val\n";
235             }
236              
237             =cut
238              
239             sub test_polynomial {
240 83     83 0 614020 my ($f, $ind) = @_;
241              
242 83 50       317 return undef unless defined wantarray;
243              
244 83         458 my ($f2, $n_hr, $d_hr) = $f->to_collected();
245              
246 83 50       404568 return undef unless defined $n_hr;
247 83 50       376 return undef unless $f2->term_type() == T_OPERATOR;
248              
249 83         490 my $denominator;
250 83 100 66     319 if ( defined($d_hr) && ($f2->type() == B_DIVISION) ) {
251 2         26 $denominator = $f2->op2();
252             }
253              
254 83         262 my $terms = $n_hr->{terms};
255 83         237 my $trees = $n_hr->{trees};
256              
257 83 100       259 if ( !defined($ind) ) {
258             # try to detect indeterminate variable
259             # get some statistics on the variables in the expression
260 67         145 my $num_terms = scalar(keys %{$terms});
  67         178  
261 67         207 my %v_freq;
262             my %v_pows;
263 67         160 while ( my ($k, $v) = each %{$terms} ) {
  331         1066  
264 264         674 foreach my $v2 (split(/,/, $k)) {
265 292         806 my ($vv, $p) = split(/:/, $v2);
266 292 100       966 if ( $vv =~ /^VAR/ ) {
267 225         558 $v_freq{$vv}++;
268 225         789 $v_pows{$p}{$vv}++;
269             }
270             }
271             }
272              
273 67 100       234 if ( scalar(keys %v_freq) == 1 ) {
274             # only one variable
275 58         196 my @v = keys %v_freq;
276 58         350 $ind = $trees->{$v[0]}{name};
277             }
278             else {
279             # find highest power
280 9         58 my @p_s = sort { $b <=> $a } keys %v_pows;
  23         71  
281 9         26 my $highest_p = $p_s[0];
282              
283             my @t3 =
284 9         28 map { $_->[0] }
285 0         0 sort { $b->[1] <=> $a->[1] }
286 9         40 map { [$_, $v_freq{$_}] }
287 9         25 keys %{$v_pows{$highest_p}};
  9         32  
288            
289 9         85 $ind = $trees->{$t3[0]}{name};
290             }
291             }
292              
293 83         191 my $var = $ind;
294 83         211 my @coeffs;
295             my @constants;
296             # save off the constant accumulator immediately
297 83         403 push @constants, Math::Symbolic::Constant->new($terms->{constant_accumulator});
298 83         1705 delete $terms->{constant_accumulator};
299              
300 83         218 my @vars = grep { $trees->{$_}{name} eq $var } grep { /^VAR/ } keys %{$trees};
  114         410  
  114         388  
  83         245  
301 83 50       337 if ( scalar(@vars) == 0 ) {
302 0         0 carp("Passed indeterminate '$ind' but no variable in expression matches.");
303 0         0 return undef;
304             }
305 83         213 my $k = $vars[0];
306            
307 83         156 my @ks = keys %{$terms};
  83         398  
308              
309             # go through every term and figure out the coefficient for this term.
310 83         225 foreach my $kss (@ks) {
311              
312             # look at all the variables, functions, etc. for this term.
313             # remove the variable we are considering
314 228         843 my @tkss = split(/,/, $kss);
315 228         451 my @n_ss = grep { $_ !~ /^$k/ } @tkss; # this is all elements in the term not the polynomial variable
  256         1655  
316              
317             # create a Math::Symbolic product tree for the coefficient
318 228         406 my @product_list;
319 228         792 push @product_list, Math::Symbolic::Constant->new( $terms->{$kss} ); # the constant multiplier for the term
320              
321             # look at the variables which remain
322 228         3950 V_LOOP: foreach my $nv (@n_ss) {
323            
324 33         102 my ($nvar, $npow) = split(/:/, $nv);
325 33 50       94 next V_LOOP if $npow == 0;
326              
327 33 50       102 if ( $npow == 1 ) {
328 33         156 push @product_list, $trees->{$nvar}->new();
329             }
330             else {
331 0         0 push @product_list, Math::Symbolic::Operator->new('^', $trees->{$nvar}->new(), Math::Symbolic::Constant->new($npow));
332             }
333             }
334              
335 228         923 my $ntp = shift @product_list;
336 228         624 while (@product_list) {
337 33         58 my $e = shift @product_list;
338 33         93 $ntp = Math::Symbolic::Operator->new( '*', $ntp, $e );
339             }
340              
341 228 100       1262 if ( scalar(@n_ss) == scalar(@tkss) ) {
342             # no instance of the indeterminate in this term. Put it into the constant
343 5         18 push @constants, $ntp;
344             }
345             else {
346 223         437 my @pkss = grep { $_ =~ /^$k/ } @tkss; # extract the element corresponding to the polynomial variable
  251         1354  
347 223         746 my ($v, $p) = split(/:/, $pkss[0]); # there should be only one.
348            
349             # save this coefficient in a slot corresponding to the index of the indeterminate in this term
350 223 100       638 if ( defined($coeffs[$p]) ) {
351 9         46 $coeffs[$p] += $ntp;
352             }
353             else {
354 214         791 $coeffs[$p] = $ntp;
355             }
356             }
357             }
358            
359             # deal with the constant terms
360 83         314 my $const = shift @constants;
361 83         264 while (@constants) {
362 5         10 my $e = shift @constants;
363 5         37 $const = Math::Symbolic::Operator->new( '+', $const, $e );
364             }
365              
366 83         305 $coeffs[0] = $const;
367              
368 83 100       219 if ( defined $denominator ) {
369 2         9 foreach my $p (0..scalar(@coeffs)-1) {
370 6         217 $coeffs[$p] /= $denominator;
371             }
372             }
373              
374 83 50       270 if ( defined $var ) {
375             # post-process the extracted coefficients. Simplify coefficients and set undefined coefficients to 0
376 83         320 foreach my $p (0..scalar(@coeffs)-1) {
377 314 100 66     60262 if ( defined($coeffs[$p]) && (ref($coeffs[$p]) =~ /^Math::Symbolic/) ) {
    50          
378 297         2051 $coeffs[$p] = $coeffs[$p]->to_collected(); # simplify this coefficient as much as possible
379             }
380             elsif ( !defined($coeffs[$p]) ) {
381 17         74 $coeffs[$p] = Math::Symbolic::Constant->new(0);
382             }
383             }
384             }
385             else {
386 0         0 return undef;
387             }
388              
389 83         22003 @coeffs = reverse @coeffs;
390              
391             # root equations and discriminant
392             # TODO: cubic and quartic(!?)
393 83         258 my @roots;
394             my $discriminant;
395 83 100       292 if ( scalar(@coeffs) == 3 ) {
396 35         126 my ($qa, $qb, $qc) = @coeffs;
397              
398             # discriminant formula
399             # b^2 - 4*a*c
400 35         183 $discriminant = parse_from_string("b^2 - 4*a*c");
401 35         454980 $discriminant->implement( 'a' => $qa, 'b' => $qb, 'c' => $qc );
402 35         24686 $discriminant = $discriminant->to_collected();
403              
404             # quadratic formula
405             # (-b +- sqrt(b^2 - 4*a*c))/2a
406 35         94130 my $qeq1 = parse_from_string("(-1*b + sqrt(discriminant))/(2*a)");
407 35         2005225 $qeq1->implement( 'a' => $qa, 'b' => $qb, 'discriminant' => $discriminant );
408 35         28684 my $qeq1_c = $qeq1->to_collected();
409 35 50       147860 $qeq1 = $qeq1_c if defined $qeq1_c;
410              
411 35         477 my $qeq2 = parse_from_string("(-1*b - sqrt(discriminant))/(2*a)");
412 35         2006055 $qeq2->implement( 'a' => $qa, 'b' => $qb, 'discriminant' => $discriminant );
413 35         30632 my $qeq2_c = $qeq2->to_collected();
414 35 50       198067 $qeq2 = $qeq2_c if defined $qeq2_c;
415              
416 35         502 @roots = ($qeq1, $qeq2);
417             }
418              
419 83 100       6020 return wantarray ? ($var, \@coeffs, $discriminant, \@roots) : [$var, \@coeffs, $discriminant, \@roots];
420             }
421              
422             =head1 method apply_synthetic_division()
423              
424             Exported through the Math::Symbolic module extension class. Divides the target polynomial by a divisor of the
425             form (x - r) using synthetic division. This method relies on test_polynomial() to detect if the target
426             Math::Symbolic expression is a polynomial, and to determine the coefficients and the indeterminate variable if
427             not provided.
428              
429             Takes two parameters, the evaluator i.e. the 'r' part of (x-r) (required, can be a Math::Symbolic expression or a
430             text string which will be converted to a Math::Symbolic expression using the parser), and the polynomial variable
431             (optional, as test_polynomial() can try to detect it).
432              
433             If called in a scalar context, it returns the polynomial in its divided form, i.e. D*Q + R (where D is the divisor
434             x-r, Q is the quotient (polynomial) and R is the remainder).
435              
436             If called in a list context, it returns the polynomial in divided form as describe above, and also the divisor,
437             quotient and remainder expressions.
438              
439             Example:
440              
441             use strict;
442             use warnings;
443             use Math::Symbolic qw(:all);
444             use Math::Symbolic::Custom::Polynomial;
445              
446             # Divide (2*x^3 - 6*x^2 + 2*x - 1) by (x - 3)
447             my $poly = parse_from_string("2*x^3 - 6*x^2 + 2*x - 1");
448             my $evaluator = 3; # it will put "x - $evaluator" internally.
449              
450             # Specifying 'x' as the polynomial variable in apply_synthetic_division() is optional, see test_polynomial().
451             # It is not needed for this straightforward polynomial but is just present for documentation.
452             my ($full_expr, $divisor, $quotient, $remainder) = $poly->apply_synthetic_division($evaluator, 'x');
453              
454             # The return values are Math::Symbolic expressions
455             print "Full expression: $full_expr\n"; # Full expression: ((x - 3) * (2 + (2 * (x ^ 2)))) + 5
456             print "Divisor: $divisor\n"; # Divisor: x - 3
457             print "Quotient: $quotient\n"; # Quotient: 2 + (2 * (x ^ 2))
458             print "Remainder: $remainder\n"; # Remainder: 5
459              
460             # Also works symbolically. Divide it by r.
461             ($full_expr, $divisor, $quotient, $remainder) = $poly->apply_synthetic_division('r');
462              
463             print "Full expression: $full_expr\n";
464             # Full expression: ((x - r) * (((((2 + (2 * (r ^ 2))) + (2 * (x ^ 2))) + ((2 * r) * x)) - (6 * r)) - (6 * x))) + ((((2 * r) + (2 * (r ^ 3))) - 1) - (6 * (r ^ 2)))
465             print "Divisor: $divisor\n"; # Divisor: x - r
466             print "Quotient: $quotient\n"; # Quotient: ((((2 + (2 * (r ^ 2))) + (2 * (x ^ 2))) + ((2 * r) * x)) - (6 * r)) - (6 * x)
467             print "Remainder: $remainder\n"; # Remainder: (((2 * r) + (2 * (r ^ 3))) - 1) - (6 * (r ^ 2))
468              
469             =cut
470              
471             sub apply_synthetic_division {
472 9     9 0 466 my ($f, $r, $ind) = @_;
473              
474 9 50       39 return undef unless defined $r;
475              
476 9 50       58 $r = parse_from_string($r) unless ref($r) =~ /^Math::Symbolic/;
477 9 50       16049 return undef unless defined $r;
478              
479 9         117 my ($var, $co) = $f->test_polynomial($ind);
480              
481 9 50       39 return undef unless defined $var;
482              
483 9         19 my @coeffs = @{$co};
  9         32  
484 9         24 my $degree = scalar(@coeffs)-1;
485              
486 9 50       30 return undef if $degree < 2;
487              
488 9         24 my @quotient; # Coefficients of Q
489             my $remainder; # Remainder R
490            
491             # Initialize with the leading coefficient
492 9         19 my $carry = shift @coeffs;
493 9         58 push @quotient, $carry;
494              
495             # Perform synthetic division
496 9         25 foreach my $coeff (@coeffs) {
497 31         92 $carry = Math::Symbolic::Operator->new('+', $coeff, Math::Symbolic::Operator->new('*', $carry, $r));
498 31         1373 push @quotient, $carry;
499             }
500              
501             # The last carry is the remainder
502 9         22 $remainder = pop @quotient;
503 9         60 $remainder = $remainder->to_collected();
504              
505 9         10763 my $q_poly = symbolic_poly($var, \@quotient);
506 9         93 $q_poly = $q_poly->to_collected();
507              
508 9         37657 my $divisor = Math::Symbolic::Operator->new('-', Math::Symbolic::Variable->new($var), $r);
509 9         356 my $full_expr = Math::Symbolic::Operator->new('+', Math::Symbolic::Operator->new('*', $divisor, $q_poly), $remainder);
510              
511 9 50       548 return wantarray ? ($full_expr, $divisor, $q_poly, $remainder) : $full_expr;
512             }
513              
514             =head1 method apply_polynomial_division()
515              
516             Exported through the Math::Symbolic module extension class. Divides the target polynomial by a divisor polynomial
517             using polynomial long division. This method relies on test_polynomial() to detect if the target and divisor
518             expressions are polynomials, and to determine the coefficients and the indeterminate variables if not provided.
519             The indeterminate variables must be the same in both expressions.
520              
521             Takes two parameters, the divisor polynomial (required, can be a Math::Symbolic expression or a text string which
522             will be converted to a Math::Symbolic expression using the parser), and the polynomial variable (optional, as
523             test_polynomial() can try to detect it).
524              
525             If called in a scalar context, it returns the polynomial in its divided form, i.e. D*Q + R (where D is the divisor,
526             Q is the quotient and R is the remainder).
527              
528             If called in a list context, it returns the polynomial in divided form as describe above, and also the divisor,
529             quotient and remainder expressions.
530              
531             Example:
532              
533             use strict;
534             use warnings;
535             use Math::Symbolic qw(:all);
536             use Math::Symbolic::Custom::Polynomial;
537              
538             # Divide (2*y^3 - 3*y^2 - 3*y +2) by (y - 2)
539             my $poly = symbolic_poly('y', [2, -3, -3, 2]);
540             my ($full_expr, $divisor, $quotient, $remainder) = $poly->apply_polynomial_division('y-2', 'y');
541              
542             print "Full expression: $full_expr\n"; # Full expression: (y - 2) * ((y + (2 * (y ^ 2))) - 1)
543             print "Divisor: $divisor\n"; # Divisor: y - 2
544             print "Quotient: $quotient\n"; # Quotient: (y + (2 * (y ^ 2))) - 1
545             print "Remainder: $remainder\n"; # Remainder: 0
546              
547             # Also works symbolically. Divide by (y^2 - 2*k*y + k)
548             ($full_expr, $divisor, $quotient, $remainder) = $poly->apply_polynomial_division('y^2 - 2*k*y + k', 'y');
549              
550             print "Full expression: $full_expr\n";
551             # Full expression: ((((y ^ 2) - ((2 * k) * y)) + k) * (((4 * k) + (2 * y)) - 3)) + (((((2 + (3 * k)) + ((8 * (k ^ 2)) * y)) - (4 * (k ^ 2))) - (3 * y)) - ((8 * k) * y))
552             print "Divisor: $divisor\n"; # Divisor: ((y ^ 2) - ((2 * k) * y)) + k
553             print "Quotient: $quotient\n"; # Quotient: ((4 * k) + (2 * y)) - 3
554             print "Remainder: $remainder\n"; # Remainder: ((((2 + (3 * k)) + ((8 * (k ^ 2)) * y)) - (4 * (k ^ 2))) - (3 * y)) - ((8 * k) * y)
555              
556             =cut
557              
558             sub apply_polynomial_division {
559 20     20 0 1086 my ($f, $divisor, $ind) = @_;
560              
561 20 50       109 return undef unless defined $divisor;
562              
563 20 50       111 $divisor = parse_from_string($divisor) unless ref($divisor) =~ /^Math::Symbolic/;
564 20 50       76 return undef unless defined $divisor;
565              
566 20         171 my ($var1, $co1) = $f->test_polynomial($ind);
567 20 50       97 return undef unless defined $var1;
568              
569 20         168 my ($var2, $co2) = $divisor->test_polynomial($ind);
570 20 50       230 return undef unless defined $var2;
571              
572 20 50       74 return undef unless $var1 eq $var2;
573              
574 20         83 my @dividend = @{$co1}; # Coefficients of the dividend polynomial
  20         62  
575 20         47 my @divisor = @{$co2}; # Coefficients of the divisor polynomial
  20         47  
576            
577 20         60 my $dividend_degree = scalar(@dividend) - 1;
578 20         53 my $divisor_degree = scalar(@divisor) - 1;
579            
580 20         73 my @quotient = ();
581 20         79 my @remainder = @dividend;
582              
583             # Perform division until the degree of remainder is less than the divisor's degree
584 20         82 while (scalar(@remainder) - 1 >= $divisor_degree) {
585              
586 71         332 my $lead_coeff_ratio = Math::Symbolic::Operator->new('/', $remainder[0], $divisor[0]);
587 71         1912 push @quotient, $lead_coeff_ratio;
588              
589 71         189 my $degree_diff = scalar(@remainder) - scalar(@divisor);
590              
591             # Subtract the product of the divisor and the term from the remainder
592 71         222 my @scaled_divisor = map { Math::Symbolic::Operator->new('*', $_, $lead_coeff_ratio) } (@divisor, (0) x $degree_diff);
  271         92872  
593 71         101874 @remainder = map { Math::Symbolic::Operator->new('-', $remainder[$_], $scaled_divisor[$_]) } (0..scalar(@remainder)-1);
  271         4976  
594            
595 71         2030 shift @remainder;
596             }
597              
598             # Collect up the new coefficients
599 20         95 @quotient = map { scalar($_->to_collected()) } @quotient;
  71         44737  
600 20         53773 @remainder = map { scalar($_->to_collected()) } @remainder;
  30         52760  
601              
602 20         109075 my $q_poly = symbolic_poly($var1, \@quotient);
603 20 50       76 return undef unless defined $q_poly;
604 20         181 $q_poly = $q_poly->to_collected();
605              
606 20         63482 my $r_poly = symbolic_poly($var1, \@remainder);
607              
608 20         51 my $full_expr;
609 20 100 100     155 if ( defined($r_poly) && !$r_poly->is_identical(Math::Symbolic::Constant->new(0)) ) {
610 9         809 $r_poly = $r_poly->to_collected();
611 9         5453 $full_expr = Math::Symbolic::Operator->new('+', Math::Symbolic::Operator->new('*', $divisor, $q_poly), $r_poly);
612             }
613             else {
614 11         693 $full_expr = Math::Symbolic::Operator->new('*', $divisor, $q_poly);
615 11 100       375 $r_poly = Math::Symbolic::Constant->new(0) unless defined $r_poly;
616             }
617              
618 20 50       944 return wantarray ? ($full_expr, $divisor, $q_poly, $r_poly) : $full_expr;
619             }
620              
621             =head1 SEE ALSO
622              
623             L
624              
625             L
626              
627             L
628              
629             =head1 AUTHOR
630              
631             Matt Johnson, C<< >>
632              
633             =head1 ACKNOWLEDGEMENTS
634              
635             Steffen Mueller, author of Math::Symbolic
636              
637             =head1 LICENSE AND COPYRIGHT
638              
639             This software is copyright (c) 2024 by Matt Johnson.
640              
641             This is free software; you can redistribute it and/or modify it under
642             the same terms as the Perl 5 programming language system itself.
643              
644             =cut
645              
646             1;
647             __END__