File Coverage

blib/lib/Math/LinearCombination.pm
Criterion Covered Total %
statement 97 125 77.6
branch 25 42 59.5
condition 13 26 50.0
subroutine 17 22 77.2
pod 17 17 100.0
total 169 232 72.8


line stmt bran cond sub pod time code
1             package Math::LinearCombination;
2              
3             require 5.005_62;
4 2     2   12933 use strict;
  2         4  
  2         97  
5 2     2   13 use warnings;
  2         4  
  2         80  
6 2     2   23 use Carp;
  2         4  
  2         223  
7             our ($VERSION);
8             $VERSION = '0.03';
9             use fields (
10 2         13 '_entries', # hash sorted on variable id's, with a ref to hash for
11             # each variable-coefficient pair:
12             # { var => $var_object, coeff => $num_coefficient }
13 2     2   2669 );
  2         3907  
14              
15             use overload
16 2         18 '+' => 'add',
17             '-' => 'subtract',
18             '*' => 'mult',
19             '/' => 'div',
20 2     2   3146 "\"\"" => 'stringify';
  2         1689  
21              
22             ### Object builders
23             sub new {
24             # parse the arguments
25 4     4 1 6371 my $proto = shift;
26 4   66     23 my $pkg = ref($proto) || $proto;
27 4 100 66     41 if(@_ == 1
    50 66        
28             && defined(ref $_[0])
29             && $_[0]->isa('Math::LinearCombination')) {
30             # new() has been invoked as a copy ctor
31 1         4 return $_[0]->clone();
32             }
33             elsif(@_) {
34 0         0 croak "Invalid nr. of arguments passed to new()";
35             }
36              
37             # construct the object
38 3         10 my Math::LinearCombination $this = fields::new($pkg);
39              
40             # apply default values
41 3         166 $this->{_entries} = {};
42              
43 3         9 $this;
44             }
45              
46             sub make {
47             # alternative constructor, which accepts a sequence (var1, coeff1, var2, coeff2, ...)
48             # as an initializer list
49 0     0 1 0 my $proto = shift;
50 0   0     0 my $pkg = ref($proto) || $proto;
51 0         0 my $ra_args = \@_;
52 0 0 0     0 if(defined($ra_args->[0])
      0        
53             && defined(ref $ra_args->[0])
54             && ref($ra_args->[0]) eq 'ARRAY') {
55 0         0 $ra_args = $ra_args->[0]; # argument array was passed as a ref
56             };
57 0         0 my $this = new $pkg;
58 0         0 while(@$ra_args) {
59 0         0 my $var = shift @$ra_args;
60 0 0       0 defined(my $coeff = shift @$ra_args) or die "Odd number of arguments";
61 0         0 $this->add_entry(var => $var, coeff => $coeff);
62             }
63 0         0 return $this;
64             }
65              
66             sub clone {
67 1     1 1 2 my Math::LinearCombination $this = shift;
68 1         16 my Math::LinearCombination $clone = $this->new();
69 1         4 $clone->add_inplace($this);
70 1         3 return $clone;
71             }
72              
73             sub add_entry {
74 11     11 1 1286 my Math::LinearCombination $this = shift;
75 3         10 my %arg = (@_ == 1 && defined(ref $_[0]) && ref($_[0]) eq 'HASH')
76 11 100 66     64 ? %{$_[0]} : @_;
77              
78 11 100       120 exists $arg{var} or croak "No `var' argument given to add_entry()";
79 10         14 my $var = $arg{var};
80 10 100       110 UNIVERSAL::can($var,'id') or croak "Given `var' argument has no id() method";
81 9 50       25 UNIVERSAL::can($var,'name') or croak "Given `var' argument has no name() method";
82 9 50       27 UNIVERSAL::can($var,'evaluate') or croak "Given `var' argument has no evaluate() method";
83              
84 9 100       265 exists $arg{coeff} or croak "No `coeff' argument given to add_entry()";
85 8         10 my $coeff = $arg{coeff};
86              
87 8   100     22 my $entry = $this->{_entries}->{$var->id()} ||= {};
88 8 100       76 if(exists $entry->{var}) { # we're adding to an existing entry
89 2 100       88 $entry->{var} == $var or
90             croak "add_entry() found distinct variable with same id";
91             }
92             else { # we're initializing a new entry
93 6         11 $entry->{var} = $var;
94             }
95 7         12 $entry->{coeff} += $coeff;
96              
97 7         17 return;
98             }
99              
100             ### Accessors
101             sub get_entries {
102 1     1 1 2 my Math::LinearCombination $this = shift;
103 1         4 return $this->{_entries};
104             }
105              
106             sub get_variables {
107 1     1 1 3 my Math::LinearCombination $this = shift;
108 1         3 my @vars = map { $this->{_entries}->{$_}->{var} } sort keys %{$this->{_entries}};
  2         10  
  1         5  
109 1 50       7 return wantarray ? @vars : \@vars;
110             }
111              
112             sub get_coefficients {
113 1     1 1 239 my Math::LinearCombination $this = shift;
114 1         3 my @coeffs = map { $this->{_entries}->{$_}->{coeff} } sort keys %{$this->{_entries}};
  2         8  
  1         6  
115 1 50       20 return wantarray ? @coeffs : \@coeffs;
116             }
117              
118             ### Mathematical manipulations
119             sub add_inplace {
120 2     2 1 204 my Math::LinearCombination $this = shift;
121 2         2 my Math::LinearCombination $arg = shift;
122 2         3 while(my($id,$entry) = each %{$arg->{_entries}}) {
  5         19  
123 3         7 $this->add_entry($entry);
124             }
125 2         5 $this->remove_zeroes();
126 2         4 return $this;
127             }
128              
129             sub add {
130 0     0 1 0 my ($a,$b) = @_;
131 0         0 my $sum = $a->clone();
132 0         0 $sum->add_inplace($b);
133 0         0 return $sum;
134             }
135              
136             sub subtract {
137 0     0 1 0 my ($a,$b,$flip) = @_;
138 0 0       0 my $diff = $flip ? $a->clone() : $b->clone(); # the negative term ...
139 0         0 $diff->negate_inplace(); # ... is negated
140 0 0       0 $diff->add_inplace($flip ? $b : $a); # and the positive term is added
141 0         0 return $diff;
142             }
143              
144             sub negate_inplace {
145 1     1 1 3 my Math::LinearCombination $this = shift;
146 1         4 $this->multiply_with_constant_inplace(-1.0);
147 1         2 return $this;
148             }
149              
150             sub multiply_with_constant_inplace {
151 2     2 1 2 my Math::LinearCombination $this = shift;
152 2         4 my $constant = shift;
153 2         3 while(my($id,$entry) = each %{$this->{_entries}}) {
  7         19  
154 5         10 $entry->{coeff} *= $constant;
155             }
156 2         12 $this->remove_zeroes();
157 2         4 return $this;
158             }
159              
160             sub mult {
161 0     0 1 0 my ($a,$b) = @_;
162 0         0 my $prod = $a->clone(); # clones the linear combination
163 0         0 $prod->multiply_with_constant_inplace($b); # multiplies with the scalar
164 0         0 return $prod;
165             }
166              
167             sub div {
168 0     0 1 0 my ($a,$b,$flip) = @_;
169 0 0       0 die "Unable to divide a scalar (or anything else) by a " . ref($a) . ". Stopped"
170             if $flip;
171 0         0 return $a->mult(1.0/$b);
172             }
173              
174             sub evaluate {
175 3     3 1 8 my Math::LinearCombination $this = shift;
176 3         7 my $val = 0.0;
177 3         5 while(my($id,$entry) = each %{$this->{_entries}}) {
  9         74  
178 6         44 $val += $entry->{var}->evaluate() * $entry->{coeff};
179             }
180 3         12 return $val;
181             }
182              
183             sub remove_zeroes {
184 5     5 1 6 my Math::LinearCombination $this = shift;
185 5         5 my @void_ids = grep { $this->{_entries}->{$_}->{coeff} == 0.0 } keys %{$this->{_entries}};
  12         34  
  5         58  
186 5         9 delete $this->{_entries}->{$_} foreach @void_ids;
187 5         9 return;
188             }
189              
190             ### I/O
191             sub stringify {
192 6     6 1 466 my Math::LinearCombination $this = shift;
193              
194 6         8 my @str_entries;
195 6         7 foreach my $key (sort keys %{$this->{_entries}}) {
  6         25  
196 14         23 my $var = $this->{_entries}->{$key}->{var};
197 14         22 my $coeff = $this->{_entries}->{$key}->{coeff};
198 14         13 my $str_entry = '';
199 14 100 100     41 if($coeff < 0.0 || @str_entries) { # adds the sign only if needed
200 12 100       24 $str_entry .= $coeff > 0.0 ? '+' : '-';
201             }
202 14 50       25 if(abs($coeff) != 1.0) { # adds the coefficient value if not +1 or -1
203 14         60 $str_entry .= sprintf("%g ", abs($coeff));
204             }
205 14         32 $str_entry .= $var->name();
206 14         66 push @str_entries, $str_entry;
207             }
208              
209 6 50       37 return @str_entries ? join(' ', @str_entries) : '0.0';
210             }
211              
212             1;
213              
214             __END__