File Coverage

blib/lib/Math/Random/AcceptReject.pm
Criterion Covered Total %
statement 15 64 23.4
branch 0 28 0.0
condition 0 6 0.0
subroutine 5 8 62.5
pod 2 2 100.0
total 22 108 20.3


line stmt bran cond sub pod time code
1             package Math::Random::AcceptReject;
2              
3 1     1   19859 use 5.006;
  1         4  
  1         32  
4 1     1   6 use strict;
  1         1  
  1         45  
5 1     1   4 use warnings;
  1         2  
  1         37  
6              
7             our $VERSION = '0.02';
8              
9 1     1   5 use Carp qw/croak/;
  1         1  
  1         105  
10 1     1   745 use Math::Symbolic qw/parse_from_string/;
  1         154831  
  1         1992  
11              
12             =head1 NAME
13              
14             Math::Random::AcceptReject - Acceptance-Rejection PDF transformations
15              
16             =head1 SYNOPSIS
17              
18             use Math::Random::AcceptReject;
19             my $pdf = Math::Random::AcceptReject->new(
20             xmin => 0, # defaults to 0
21             xmax => 2, # defaults to 1
22             ymax => 2, # no default!
23             pdf => 'x', # a triangle from x=0 to x=2 with slope 1
24             random => 'rand', # use Perl's builtin (default)
25             );
26            
27             foreach (1..100) {
28             my $rnd = $pdf->rand();
29             # ...
30             }
31            
32             # Use Math::Random::MT instead of bultin rand()
33             # Same target PDF but as Perl code instead of a Math::Symbolic
34             # function!
35             use Math::Random::MT;
36             my $mt = Math::Random::Mt->new($seed);
37             $pdf = Math::Random::AcceptReject->new(
38             xmax => 2,
39             ymax => 2,
40             pdf => sub { $_[0] },
41             random => sub { $mt->rand() };
42             );
43              
44             =head1 DESCRIPTION
45              
46             This module implements acceptance-rejection transformations of uniformly
47             distributed random numbers to mostly arbitrary probability density functions
48             (Is).
49              
50             Note that whereas J. von Neumann's algorithm can transform from arbitrary
51             source PDFs to arbitrary destination PDFs, this module is currently limited to
52             uniform C<[0,1]> source PDFs!
53              
54             =head1 METHODS
55              
56             =cut
57              
58             =head2 new
59              
60             Creates a new random number generator. Takes named arguments.
61              
62             Mandatory parameters:
63              
64             pdf: The probability density function. This can either be a
65             subroutine reference which takes an argument ('x') and
66             returns f(x), a Math::Symbolic tree representing f(x) and
67             using the variable 'x', or a string which can be parsed
68             as such a Math::Symbolic tree.
69             ymax: Maximum value of the target PDF f(x) in the x range. This
70             max theoretically be safely set to a very large value which
71             is much higher than the real maximum of f(x) within
72             the range [xmin,xmax]. The efficiency of the algorithm goes
73             down with
74              
75             Optional parameters:
76              
77             random: The random number generator. Defaults to using Perl's
78             rand() function. May be set to either 'rand' for the
79             default or a subroutine reference for custom random
80             number generators. Expected to return one or more(!)
81             random numbers per call.
82             xmin: Minimum value for x. Defaults to 0.
83             xmax: Maximum value for x. Defaults to 1.
84              
85             =cut
86              
87             sub _dor {
88 0     0     foreach (@_) {
89 0 0         return $_ if defined $_;
90             }
91 0           return();
92             }
93              
94             sub new {
95 0     0 1   my $proto = shift;
96 0   0       my $class = ref($proto)||$proto;
97              
98 0           my %args = @_;
99              
100             # Argument checking
101 0 0         if (not defined $args{ymax}) {
102 0           croak("Need 'ymax' parameter.");
103             }
104 0 0         if (not ref $args{random} eq 'CODE') {
105 0           $args{random} = 'rand';
106             }
107 0 0         if (not defined $args{pdf}) {
108 0           croak("Need 'pdf' parameter.");
109             }
110 0 0         if (not ref($args{pdf})) {
111 0           eval {
112 0           $args{pdf} = parse_from_string($args{pdf});
113             };
114 0 0 0       if ($@ or not defined $args{pdf}) {
115 0 0         croak(
116             "Error parsing string into Math::Symbolic tree."
117             . ($@ ? " Error: $@" : "")
118             );
119             }
120             }
121 0 0         if (ref($args{pdf}) =~ /^Math::Symbolic/) {
122 0           my ($sub, $leftover) = $args{pdf}->to_sub(x => 0);
123 0 0         die("Compiling Math::Symbolic tree to sub failed!")
124             if not ref($sub) eq 'CODE';
125 0           $args{pdf} = $sub;
126             }
127 0 0         if (not ref($args{pdf}) eq 'CODE') {
128 0           croak("'pdf' parameter needs to be a CODE ref, string, or Math::Symbolic tree.");
129             }
130              
131 0           my $self = {
132             xmin => _dor($args{xmin}, 0),
133             xmax => _dor($args{xmax}, 1),
134             ymax => $args{ymax},
135             random => $args{random},
136             pdf => $args{pdf},
137             cache => [],
138             };
139              
140 0 0         if ($self->{xmin} >= $self->{xmax}) {
141 0           croak("'xmin' must be smaller than 'xmax'");
142             }
143              
144 0           $self->{xdiff} = $self->{xmax} - $self->{xmin};
145              
146 0           bless $self => $class;
147              
148 0           return $self;
149             }
150              
151             =head2 rand
152              
153             Returns the next random number of PDF C as specified by the C
154             parameter to C.
155              
156             =cut
157              
158             sub rand {
159 0     0 1   my $self = shift;
160 0           my $rnd = $self->{random};
161 0           my $pdf = $self->{pdf};
162 0           my $cache = $self->{cache};
163              
164 0           my $accept = 0;
165 0           my $u = 0;
166 0           my $f = -1;
167 0           my $x;
168 0 0         if (ref($rnd) eq 'CODE') {
169 0           while ($u > $f) {
170 0 0         push @$cache, $rnd->() if not @$cache;
171 0           $x = $self->{xmin} + shift(@$cache) * $self->{xdiff};
172            
173 0           $f = $pdf->($x);
174            
175 0 0         push @$cache, $rnd->() if not @$cache;
176 0           $u = shift(@$cache) * $self->{ymax};
177             }
178             }
179             else { # rand
180 0           while ($u > $f) {
181 0           $x = $self->{xmin} + rand() * $self->{xdiff};
182 0           $f = $pdf->($x);
183 0           $u = rand() * $self->{ymax};
184             }
185             }
186              
187 0           return $x;
188             }
189              
190             1;
191             __END__