File Coverage

blib/lib/Statistics/Distribution/Generator.pm
Criterion Covered Total %
statement 133 199 66.8
branch 17 34 50.0
condition 6 19 31.5
subroutine 38 53 71.7
pod 7 7 100.0
total 201 312 64.4


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