File Coverage

blib/lib/Math/Polynomial/Multivariate.pm
Criterion Covered Total %
statement 288 288 100.0
branch 66 66 100.0
condition 23 27 85.1
subroutine 45 45 100.0
pod 18 18 100.0
total 440 444 99.1


line stmt bran cond sub pod time code
1             # Copyright (c) 2011-2014 Martin Becker. All rights reserved.
2             # This package is free software; you can redistribute it and/or modify it
3             # under the same terms as Perl itself.
4             #
5             # $Id: Multivariate.pm 17 2014-02-21 12:51:52Z demetri $
6              
7             package Math::Polynomial::Multivariate;
8              
9 5     5   899002 use 5.008;
  5         21  
  5         202  
10 5     5   38 use strict;
  5         10  
  5         901  
11 5     5   35 use warnings;
  5         14  
  5         175  
12 5     5   28 use Carp qw(croak);
  5         8  
  5         732  
13              
14             use overload (
15 5         84 q[neg] => \&_neg,
16             q[+] => \&_add,
17             q[-] => \&_sub,
18             q[*] => \&_mul,
19             q[**] => \&_pow,
20             q[==] => \&_eq,
21             q[!=] => \&_ne,
22             q[!] => 'is_null',
23             q[bool] => 'is_not_null',
24             q[""] => 'as_string',
25             q[fallback] => undef,
26 5     5   5065 );
  5         3319  
27              
28             # ----- object definition -----
29              
30             # Math::Polynomial::Multivariate=ARRAY(...)
31              
32             # .......... index .......... # .......... value ..........
33 5     5   1048 use constant F_TERMS => 0; # monomial terms hashref
  5         8  
  5         417  
34 5     5   27 use constant F_ZERO => 1; # zero element of coefficient space
  5         9  
  5         236  
35 5     5   27 use constant F_ONE => 2; # unit element of coefficient space
  5         7  
  5         220  
36 5     5   25 use constant NFIELDS => 3;
  5         8  
  5         324  
37              
38             # a term value is an anonymous arrayref:
39             # .......... index .......... # .......... value ..........
40 5     5   75 use constant T_COEFF => 0; # coefficient
  5         11  
  5         219  
41 5     5   24 use constant T_VARS => 1; # variables hashref, mapping names to exponents
  5         9  
  5         250  
42              
43 5     5   24 use constant K_SEP => chr 0;
  5         8  
  5         451  
44 5     5   25 use constant K_SEP_RE => qr/\0/;
  5         8  
  5         378  
45              
46 5     5   37 use constant _MINUS_INFINITY => - (~0) ** (~0);
  5         9  
  5         19789  
47              
48             # ----- class data -----
49              
50             our $VERSION = '0.005';
51              
52             # ----- private subroutines -----
53              
54             # create a "clean" copy of a variables hashref, without zero exponents
55             sub _vclean {
56 14     14   17 my %vars = %{$_[0]};
  14         59  
57 14         40 delete @vars{grep {!$vars{$_}} keys %vars};
  18         60  
58             die "hash of integer exponents expected"
59 14 100       43 if grep {!eval { $_ == int abs $_ }} values %vars;
  13         22  
  13         67  
60 13         32 return \%vars;
61             }
62              
63             # map a clean variables hashref to a key
64             sub _key {
65 108     108   196 my ($vars) = @_;
66             return
67 92         473 join K_SEP,
68 108         384 map { ($_, chr $vars->{$_}) }
69 108         130 reverse sort keys %{$vars};
70             }
71              
72             # map an arbitrary variables hashref to a key
73             sub _keyvc {
74 8     8   16 my ($vars) = @_;
75             return
76 6         30 join K_SEP,
77 6         19 map { ($_, chr $vars->{$_}) }
78             reverse sort
79 8         30 grep { $vars->{$_} }
80 8         13 keys %{$vars};
81             }
82              
83             # ----- private methods -----
84              
85             # extract coefficient space information from an invocant and some coefficient
86             sub _space {
87 119     119   169 my ($this, $const) = @_;
88 119         182 my $class = ref $this;
89 119         145 my ($zero, $one);
90 119 100       221 if ($class) {
91 108         206 ($zero, $one) = @{$this}[F_ZERO, F_ONE];
  108         408  
92             }
93             else {
94 11         24 $class = $this;
95 11         28 $zero = $const - $const;
96 11         460 $one = $const ** 0;
97             }
98 119         916 return ($class, $zero, $one);
99             }
100              
101             # create an independent duplicate of an object
102             sub _clone {
103 48     48   77 my ($this) = @_;
104 48         81 my $terms = $this->[F_TERMS];
105             return
106 58         237 bless [
107 58         69 { map {($_ => [@{$terms->{$_}}])} keys %{$terms} },
  48         115  
  48         233  
108 48         64 @{$this}[F_ZERO, F_ONE]
109             ], ref $this;
110             }
111              
112             # overloaded operators
113              
114             sub _neg {
115 1     1   5 my ($this) = @_;
116 1         4 my $result = $this->_clone;
117 1         2 foreach my $term (values %{$result->[F_TERMS]}) {
  1         4  
118 1         3 $term->[T_COEFF] = -$term->[T_COEFF];
119             }
120 1         5 return $result;
121             }
122              
123             sub _add {
124 28     28   1825 my ($this, $that) = @_;
125 28 100 66     200 $that = $this->const($that) if !ref($that) || !$that->isa(__PACKAGE__);
126 28         73 my $result = $this->_clone;
127 28         68 my $rterms = $result->[F_TERMS];
128 28         44 my $zero = $this->[F_ZERO];
129 28         35 while (my ($key, $term) = each %{$that->[F_TERMS]}) {
  58         198  
130 30 100       67 if (exists $rterms->{$key}) {
131 7 100       27 if ($zero == ($rterms->{$key}->[T_COEFF] += $term->[T_COEFF])) {
132 1         3 delete $rterms->{$key};
133             }
134             }
135             else {
136 23         30 $rterms->{$key} = [@{$term}];
  23         619  
137             }
138             }
139 28         117 return $result;
140             }
141              
142             sub _sub {
143 14     14   41 my ($this, $that, $swap) = @_;
144 14 100 66     104 $that = $this->const($that) if !ref($that) || !$that->isa(__PACKAGE__);
145 14 100       35 ($this, $that) = ($that, $this) if $swap;
146 14         53 my $result = $this->_clone;
147 14         32 my $rterms = $result->[F_TERMS];
148 14         26 my $zero = $this->[F_ZERO];
149 14         20 while (my ($key, $term) = each %{$that->[F_TERMS]}) {
  30         215  
150 16 100       36 if (exists $rterms->{$key}) {
151 7 100       33 if ($zero == ($rterms->{$key}->[T_COEFF] -= $term->[T_COEFF])) {
152 6         1091 delete $rterms->{$key};
153             }
154             }
155             else {
156 9         21 my $rterm = $rterms->{$key} = [@{$term}];
  9         33  
157 9         38 $rterm->[T_COEFF] = -$rterm->[T_COEFF];
158             }
159             }
160 14         50 return $result;
161             }
162              
163             sub _mul {
164 40     40   701 my ($this, $that) = @_;
165 40 100 66     2575 $that = $this->const($that) if !ref($that) || !$that->isa(__PACKAGE__);
166 40         109 my $result = $this->null;
167 40         73 my $bterms = $that->[F_TERMS];
168 40         62 my $rterms = $result->[F_TERMS];
169 40         73 my $zero = $this->[F_ZERO];
170 40         95 foreach my $aterm (values %{$this->[F_TERMS]}) {
  40         106  
171 53         79 my @avars = %{$aterm->[T_VARS]};
  53         137  
172 53         69 foreach my $bterm (values %{$bterms}) {
  53         95  
173 61         263 my %vars = @avars;
174 61         137 my $const = $aterm->[T_COEFF] * $bterm->[T_COEFF];
175 61         2708 my $bvars = $bterm->[T_VARS];
176 61         68 foreach my $bv (keys %{$bvars}) {
  61         168  
177 36         99 $vars{$bv} += $bvars->{$bv};
178             }
179 61         160 my $key = _key(\%vars);
180 61 100       171 if (exists $rterms->{$key}) {
181 4         16 $rterms->{$key}->[T_COEFF] += $const;
182             }
183             else {
184 57         321 $rterms->{$key} = [$const, \%vars];
185             }
186             }
187             }
188 40         502 delete @{$rterms}{
189 40         169 grep {$zero == $rterms->{$_}->[T_COEFF]} keys %{$rterms}
  57         493  
  40         106  
190             };
191 40         160 return $result;
192             }
193              
194             sub _pow {
195 8     8   1234 my ($this, $exp, $swap) = @_;
196 8 100 100     347 croak 'illegal exponent' if $swap || $exp != int abs $exp;
197 6 100       19 return $this->const($this->[F_ONE]) if !$exp;
198 5         15 my $result = $this->_clone;
199 5         21 while (--$exp > 0) {
200 6         17 $result *= $this;
201             }
202 5         18 return $result;
203             }
204              
205             sub _eq_ne {
206 14     14   24 my ($this, $that, $eq) = @_;
207 14 100 66     93 $that = $this->const($that) if !ref($that) || !$that->isa(__PACKAGE__);
208 14         25 my $aterms = $this->[F_TERMS];
209 14         20 my $bterms = $that->[F_TERMS];
210 14 100       17 return !$eq if keys(%{$aterms}) != keys(%{$bterms});
  14         24  
  14         50  
211 10         14 while (my ($key, $term) = each %{$aterms}) {
  14         216  
212 10 100 100     80 return !$eq
213             if !exists($bterms->{$key}) ||
214             $term->[T_COEFF] != $bterms->{$key}->[T_COEFF];
215             }
216 4         18 return $eq;
217             }
218              
219             sub _eq {
220 7     7   2099 my ($this, $that) = @_;
221 7         26 return $this->_eq_ne($that, !0);
222             }
223              
224             sub _ne {
225 7     7   1556 my ($this, $that) = @_;
226 7         19 return $this->_eq_ne($that, !1);
227             }
228              
229             # ----- public methods -----
230              
231             # constructors
232              
233             sub null {
234 71     71 1 106 my ($this) = @_;
235 71         154 my ($class, $zero, $one) = $this->_space(1);
236 71         318 return bless [{}, $zero, $one], $class;
237             }
238              
239             sub const {
240 26     26 1 2319 my ($this, $value) = @_;
241 26         88 my ($class, $zero, $one) = $this->_space($value);
242 26 100       136 my %terms = $zero == $value? (): (q[] => [$value, {}]);
243 26         1133 return bless [\%terms, $zero, $one], $class;
244             }
245              
246             sub var {
247 8     8 1 42 my ($this, $varname) = @_;
248 8         28 my ($class, $zero, $one) = $this->_space(1);
249 8         60 my $vars = { $varname => 1 };
250 8         29 my $key = _key($vars);
251 8         39 my %terms = ($key => [$one, $vars]);
252 8         65 return bless [\%terms, $zero, $one], $class;
253             }
254              
255             sub monomial {
256 14     14 1 51 my ($this, $const, $vars) = @_;
257 14         39 my ($class, $zero, $one) = $this->_space($const);
258 14         39 $vars = _vclean($vars);
259 13 100       48 my %terms = $zero == $const? (): (_key($vars) => [$const, $vars]);
260 13         81 return bless[\%terms, $zero, $one], $class;
261             }
262              
263             sub subst {
264 8     8 1 1445 my ($this, $varname, $that) = @_;
265 8         26 my @exps = (-1, $this->exponents_of($varname));
266 8 100       29 return $this->null if 1 == @exps;
267 7         16 my $exp = pop @exps;
268 7         24 my $result = $this->factor_of($varname, $exp);
269 7         13 my $nexp = pop @exps;
270 7         22 while ($exp > 0) {
271 13         43 $result *= $that;
272 13 100       77 if (--$exp == $nexp) {
273 6         17 $result += $this->factor_of($varname, $exp);
274 6         36 $nexp = pop @exps;
275             }
276             }
277 7         33 return $result;
278             }
279              
280             sub partial_derivative {
281 3     3 1 1379 my ($this, $varname, $cast) = @_;
282 3         13 my $result = $this->null;
283 3         15 foreach my $exp ($this->exponents_of($varname)) {
284 9 100       27 next if !$exp;
285 6 100       20 my $const = $cast? $cast->($exp): $exp;
286 6         25 $result +=
287             $this->factor_of($varname, $exp) *
288             $this->monomial($const, { $varname => $exp-1 });
289             }
290 3         12 return $result;
291             }
292              
293             # inspection methods
294              
295 2     2 1 3 sub is_null { !keys %{$_[0]->[F_TERMS]} }
  2         13  
296 2     2 1 4 sub is_not_null { !!keys %{$_[0]->[F_TERMS]} }
  2         11  
297              
298             sub variables {
299 9     9 1 2886 my ($this) = @_;
300 9         21 my %vars = map {%{$_->[T_VARS]}} values %{$this->[F_TERMS]};
  22         26  
  22         161  
  9         40  
301 9         70 return sort keys %vars;
302             }
303              
304             sub exponents_of {
305 13     13 1 1392 my ($this, $varname) = @_;
306 13         73 my $terms = $this->[F_TERMS];
307 13         27 my %exps = ();
308 13         18 foreach my $term (values %{$terms}) {
  13         38  
309 33   100     163 $exps{$term->[T_VARS]->{$varname} || 0} = undef;
310             }
311 13         72 return sort { $a <=> $b } keys %exps;
  20         62  
312             }
313              
314             sub factor_of {
315 23     23 1 1189 my ($this, $varname, $exp) = @_;
316 23         39 my $terms = $this->[F_TERMS];
317 23         51 my $result = $this->null;
318 23         40 my $rterms = $result->[F_TERMS];
319 23         33 foreach my $term (values %{$terms}) {
  23         58  
320 68   100     227 my $aexp = $term->[T_VARS]->{$varname} || 0;
321 68 100       167 if ($aexp == $exp) {
322 27         29 my %vars = %{$term->[T_VARS]};
  27         106  
323 27         60 delete $vars{$varname};
324 27         90 $rterms->{_key(\%vars)} = [$term->[T_COEFF], \%vars];
325             }
326             }
327 23         87 return $result;
328             }
329              
330             sub coefficient {
331 8     8 1 2934 my ($this, $vars) = @_;
332 8         27 my $key = _keyvc($vars);
333 8         77 my $terms = $this->[F_TERMS];
334 8 100       49 return exists($terms->{$key})? $terms->{$key}->[T_COEFF]: $this->[F_ZERO];
335             }
336              
337             sub degree {
338 5     5 1 672 my ($this) = @_;
339 5         15 my $terms = $this->[F_TERMS];
340 5 100       9 return _MINUS_INFINITY if !keys %{$terms};
  5         36  
341 3         6 my $max_degree = 0;
342 3         7 foreach my $term (values %{$terms}) {
  3         11  
343 9         111 my $degree = 0;
344 9         13 foreach my $e (values %{$term->[T_VARS]}) {
  9         31  
345 9         21 $degree += $e;
346             }
347 9 100       39 if ($max_degree < $degree) {
348 4         14 $max_degree = $degree;
349             }
350             }
351 3         16 return $max_degree;
352             }
353              
354             sub multidegree {
355 4     4 1 1358 my ($this) = @_;
356 4         9 my $terms = $this->[F_TERMS];
357 4         8 my %result = ();
358 4         6 foreach my $term (values %{$terms}) {
  4         13  
359 4         6 while (my ($name, $exp) = each %{$term->[T_VARS]}) {
  10         43  
360 6 100 100     34 if (($result{$name} || 0) < $exp) {
361 5         14 $result{$name} = $exp;
362             }
363             }
364             }
365 4         15 return \%result;
366             }
367              
368 2     2 1 4 sub number_of_terms { scalar keys %{$_[0]->[F_TERMS]} }
  2         10  
369              
370             sub evaluate {
371 5     5 1 7462 my ($this, $values) = @_;
372 5         24 my @vars = $this->variables;
373 5 100       21 if (my @miss = grep { !exists $values->{$_} } @vars) {
  8         34  
374 2 100       6 my $s = 1 == @miss? q[]: 's';
375 2         340 croak "missing variable$s: @miss";
376             }
377 3         6 my $result = $this;
378 3         17 foreach my $varname (@vars) {
379 4         21 $result = $result->subst($varname, $this->const($values->{$varname}));
380             }
381 3         16 return $result->coefficient({});
382             }
383              
384             sub as_monomials {
385 40     40 1 2714 my ($this) = @_;
386 40         65 my $terms = $this->[F_TERMS];
387 40 100       104 return scalar keys %{$terms} if !wantarray;
  1         7  
388 39 100       48 return ([$this->[F_ZERO], {}]) if !keys %{$terms};
  39         143  
389             return
390 51         101 map {
391 51         62 my ($coeff, $vars) = @{$terms->{$_}};
  30         106  
392 51         63 [$coeff, {%{$vars}}]
  51         836  
393 30         38 } sort keys %{$terms};
394             }
395              
396             sub as_string {
397 38     38 1 14002 my ($this) = @_;
398 38         71 my $one = $this->[F_ONE];
399             return
400 57         100 join q[ + ],
401             map {
402 38         101 my ($const, $vars) = @{$_};
  57         191  
403             join q[*],
404 41         80 keys(%{$vars}) && $one == $const? (): $const,
405             map {
406 57         465 my $exp = $vars->{$_};
407 41 100       289 1 == $exp? $_: "$_^$exp"
408             }
409 57 100 100     73 sort keys %{$vars}
410             }
411             $this->as_monomials;
412             }
413              
414             1;
415             __END__