File Coverage

blib/lib/Statistics/Distribution/Generator.pm
Criterion Covered Total %
statement 70 191 36.6
branch 0 32 0.0
condition 0 19 0.0
subroutine 26 50 52.0
pod 7 7 100.0
total 103 299 34.4


line stmt bran cond sub pod time code
1             package Statistics::Distribution::Generator;
2              
3 1     1   52508 use 5.010;
  1         4  
4              
5             use overload (
6 1         8 '0+' => '_render',
7             '""' => '_render',
8             '@{}' => '_render',
9             '|' => '_add_alternative',
10             'x' => '_add_dimension',
11             fallback => 1,
12 1     1   937 );
  1         971  
13              
14 1     1   548 use List::AllUtils qw( reduce );
  1         13715  
  1         111  
15 1     1   12 use Exporter qw( import );
  1         2  
  1         41  
16 1     1   4 use vars qw( $VERSION );
  1         2  
  1         813  
17              
18             $VERSION = '1.002';
19              
20             sub logistic ();
21              
22             our @EXPORT_OK = qw( gaussian uniform logistic supplied gamma exponential dice );
23             our %EXPORT_TAGS = (all => \@EXPORT_OK);
24              
25             our $pi = 3.1415926535897932384626433832795028841971693993751;
26             our $two_pi = 2 * $pi;
27             our $e = exp 1;
28              
29             sub _render {
30 0     0     my $self = shift;
31 0 0         if ($self->{ dims }) {
    0          
32 0           return [ map { $_->_render } @{$self->{ dims }} ];
  0            
  0            
33             }
34             elsif ($self->{ alts }) {
35 0   0 0     my $accum = reduce { $a + $b } map { $_->{ weight } // 1 } @{$self->{ alts }};
  0            
  0            
  0            
36 0           my $n = rand() * $accum;
37 0           my $answer;
38 0           for my $alt (@{$self->{ alts }}) {
  0            
39 0   0       $n -= ($alt->{ weight } // 1);
40 0 0         if ($n <= 0) {
41 0           $answer = $alt->_render;
42 0           last;
43             }
44             }
45 0           return $answer;
46             }
47 0   0       die("Can't render a(n) " . (ref($self)||$self));
48             }
49              
50             sub gaussian {
51 0     0 1   my ($mean, $sigma) = @_;
52 0   0       $mean //= 0;
53 0   0       $sigma //= 1;
54 0           return bless { mean => $mean, sigma => $sigma }, 'Statistics::Distribution::Generator::gaussian';
55             }
56              
57             sub uniform {
58 0     0 1   my ($min, $max) = @_;
59 0   0       $min //= 0;
60 0   0       $max //= 1;
61 0           return bless { min => $min, max => $max }, 'Statistics::Distribution::Generator::uniform';
62             }
63              
64             sub logistic () {
65 0     0 1   return bless { }, 'Statistics::Distribution::Generator::logistic';
66             }
67              
68             sub supplied {
69 0     0 1   my ($iv) = @_;
70 0           my $rv;
71 0 0         if (ref $iv eq 'CODE') {
72 0           $rv = { code => $iv };
73             }
74             else {
75 0     0     $rv = { code => sub { return $iv } };
  0            
76             }
77 0           return bless $rv, 'Statistics::Distribution::Generator::supplied';
78             }
79              
80             sub gamma {
81 0   0 0 1   my ($order, $scale) = map { $_ // 1 } @_;
  0            
82 0           return bless {
83             order => $order,
84             scale => $scale,
85             norder => int($order),
86             }, 'Statistics::Distribution::Generator::gamma';
87             }
88              
89             sub exponential {
90 0   0 0 1   my ($lambda) = map { $_ // 1 } @_;
  0            
91 0           return bless { lambda => $lambda }, 'Statistics::Distribution::Generator::exponential';
92             }
93              
94             sub dice {
95 0     0 1   my ($numdice, $numsides) = @_;
96 0           return bless {
97             numdice => $numdice,
98             numsides => $numsides,
99             } => 'Statistics::Distribution::Generator::dice';
100             }
101              
102             sub _rand_nonzero {
103 0     0     my $rv;
104 0           1 while (!($rv = rand));
105 0           return $rv;
106             }
107              
108             sub _add_alternative {
109 0     0     my ($lhs, $rhs, $swapped) = @_;
110 0 0         ($lhs, $rhs) = ($rhs, $lhs) if $swapped;
111 0 0         $rhs = supplied($rhs) unless ref($rhs) =~ /^Statistics::Distribution::Generator/;
112 0 0         if (!exists($lhs->{ alts })) {
113 0           $lhs = {
114             alts => [ $lhs ],
115             };
116             }
117 0 0         if (!exists($rhs->{ alts })) {
118 0           $rhs = {
119             alts => [ $rhs ],
120             };
121             }
122 0           push @{$lhs->{ alts }}, @{$rhs->{ alts }};
  0            
  0            
123 0           return bless $lhs, 'Statistics::Distribution::Generator';
124             }
125              
126             sub _add_dimension {
127 0     0     my ($lhs, $rhs, $swapped) = @_;
128 0 0         ($lhs, $rhs) = ($rhs, $lhs) if $swapped;
129 0 0         $rhs = supplied($rhs) unless ref($rhs) =~ /^Statistics::Distribution::Generator/;
130 0 0         if (!exists($lhs->{ dims })) {
131 0           $lhs = {
132             dims => [ $lhs ],
133             };
134             }
135 0 0         if (!exists($rhs->{ dims })) {
136 0           $rhs = {
137             dims => [ $rhs ],
138             };
139             }
140 0           push @{$lhs->{ dims }}, @{$rhs->{ dims }};
  0            
  0            
141 0           return bless $lhs, 'Statistics::Distribution::Generator';
142             }
143              
144             1;
145              
146             package Statistics::Distribution::Generator::gaussian;
147              
148 1     1   21 use 5.010;
  1         4  
149 1     1   5 use base qw( Statistics::Distribution::Generator );
  1         1  
  1         73  
150             use overload (
151 1         10 '0+' => '_render',
152             '""' => '_render',
153             '|' => '_add_alternative',
154             'x' => '_add_dimension',
155             fallback => 1,
156 1     1   6 );
  1         1  
157              
158             sub _render {
159 0     0     my $self = shift;
160 0           my $U = rand;
161 0           my $V = rand;
162 0           return $self->{ mean } + (sqrt(-2 * log $U) * cos($two_pi * $V) * $self->{ sigma });
163             }
164              
165             1;
166              
167             package Statistics::Distribution::Generator::uniform;
168              
169 1     1   227 use 5.010;
  1         3  
170 1     1   5 use base qw( Statistics::Distribution::Generator );
  1         2  
  1         65  
171             use overload (
172 1         4 '0+' => '_render',
173             '""' => '_render',
174             '|' => '_add_alternative',
175             'x' => '_add_dimension',
176             fallback => 1,
177 1     1   5 );
  1         1  
178              
179             sub _render {
180 0     0     my $self = shift;
181 0           return ($self->{ max } - $self->{ min }) * rand() + $self->{ min };
182             }
183              
184             1;
185              
186             package Statistics::Distribution::Generator::logistic;
187              
188 1     1   144 use 5.010;
  1         3  
189 1     1   5 use base qw( Statistics::Distribution::Generator );
  1         1  
  1         68  
190             use overload (
191 1         7 '0+' => '_render',
192             '""' => '_render',
193             '|' => '_add_alternative',
194             'x' => '_add_dimension',
195             fallback => 1,
196 1     1   12 );
  1         2  
197              
198             sub _render {
199 0     0     my $self = shift;
200 0           return -log((1 / _rand_nonzero()) - 1);
201             }
202              
203             1;
204              
205             package Statistics::Distribution::Generator::supplied;
206              
207 1     1   137 use 5.010;
  1         3  
208 1     1   5 use base qw( Statistics::Distribution::Generator );
  1         1  
  1         65  
209             use overload (
210 1         3 '0+' => '_render',
211             '""' => '_render',
212             '|' => '_add_alternative',
213             'x' => '_add_dimension',
214             fallback => 1,
215 1     1   5 );
  1         1  
216              
217             sub _render {
218 0     0     my $self = shift;
219 0           return $self->{ code }->();
220             }
221              
222             1;
223              
224             package Statistics::Distribution::Generator::gamma;
225              
226 1     1   136 use 5.010;
  1         4  
227 1     1   12 use base qw( Statistics::Distribution::Generator );
  1         3  
  1         84  
228             use overload (
229 1         4 '0+' => '_render',
230             '""' => '_render',
231             '|' => '_add_alternative',
232             'x' => '_add_dimension',
233             fallback => 1,
234 1     1   6 );
  1         2  
235              
236             sub _gamma_int {
237 0     0     my $order = shift;
238 0 0         if ($order < 12){
239 0           my $prod = 1;
240 0           for (my $i=0; $i<$order; $i++){
241 0           $prod *= _rand_nonzero();
242             }
243 0           return -log($prod);
244             }
245             else {
246 0           return _gamma_large_int($order);
247             }
248             }
249              
250 0     0     sub _tan { sin($_[0]) / cos($_[0]); }
251              
252             sub _gamma_large_int {
253 0     0     my $order = shift;
254 0           my $sqrt = sqrt(2 * $order - 1);
255 0           my ($x,$y,$v);
256 0           do {
257 0           do {
258 0           $y = _tan($pi * rand);
259 0           $x = $sqrt * $y + $order - 1;
260             } while ($x <= 0);
261 0           $v = rand;
262             } while ($v > (1 + $y * $y) * exp(($order - 1) * log($x / ($order - 1)) - $sqrt * $y));
263 0           return $x;
264             }
265              
266             sub _gamma_frac {
267 0     0     my $order = shift;
268 0           my $p = $e / ($order + $e);
269 0           my ($q, $x, $u, $v);
270 0           do {
271 0           $u = rand;
272 0           $v = _rand_nonzero();
273 0 0         if ($u < $p){
274 0           $x = exp((1 / $order) * log($v));
275 0           $q = exp(-$x);
276             }
277             else {
278 0           $x = 1 - log($v);
279 0           $q = exp(($order - 1) * log($x));
280             }
281             } while (rand >= $q);
282 0           return $x;
283             }
284              
285             sub _render {
286 0     0     my $self = shift;
287 0           my $rv;
288 0 0         if ($self->{ order } == $self->{ norder }) {
    0          
289 0           $rv = $self->{ scale } * _gamma_int($self->{ norder });
290             }
291             elsif ($self->{ norder } == 0) {
292 0           $rv = $self->{ scale } * _gamma_frac($self->{ order });
293             }
294             else {
295 0           $rv = $self->{ scale } * (_gamma_int($self->{ norder }) + _gamma_frac($self->{ norder } - $self->{ order }));
296             }
297 0           return $rv;
298             }
299              
300             1;
301              
302             package Statistics::Distribution::Generator::exponential;
303              
304 1     1   430 use 5.010;
  1         5  
305 1     1   7 use base qw( Statistics::Distribution::Generator );
  1         3  
  1         93  
306             use overload (
307 1         4 '0+' => '_render',
308             '""' => '_render',
309             '|' => '_add_alternative',
310             'x' => '_add_dimension',
311             fallback => 1,
312 1     1   6 );
  1         3  
313              
314             sub _render {
315 0     0     my $self = shift;
316 0           my $rv = -log(rand) / $self->{ lambda };
317             }
318              
319             1;
320              
321             package Statistics::Distribution::Generator::dice;
322              
323 1     1   186 use 5.010;
  1         4  
324 1     1   6 use base qw( Statistics::Distribution::Generator );
  1         2  
  1         92  
325             use overload (
326 1         4 '0+' => '_render',
327             '""' => '_render',
328             '|' => '_add_alternative',
329             'x' => '_add_dimension',
330             fallback => 1,
331 1     1   8 );
  1         2  
332              
333             sub _render {
334 0     0     my $self = shift;
335 0           my $rv;
336 0           for (1 .. $self->{ numdice }) {
337 0           $rv += int(1 + rand($self->{ numsides }));
338             }
339 0           return $rv;
340             }
341              
342             1;
343              
344             __END__