capricious 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,8 @@
1
+ version 0.1.0
2
+
3
+ * added multiply-with-carry based PRNG (algorithm due to George Marsaglia)
4
+ * added Poisson distribution
5
+
1
6
  version 0.0.2
2
7
 
3
8
  * initial release
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
data/lib/capricious.rb CHANGED
@@ -17,5 +17,7 @@
17
17
  # limitations under the License.
18
18
 
19
19
  require 'capricious/lfsr'
20
+ require 'capricious/mwc5'
20
21
  require 'capricious/sample_sink'
21
22
  require 'capricious/uniform'
23
+ require 'capricious/poisson'
@@ -0,0 +1,53 @@
1
+ # capricious/generic_prng.rb: generic PRNG mixin with selectable source-randomness
2
+ #
3
+ # Copyright (c) 2010 Red Hat, Inc.
4
+ #
5
+ # Author: William Benton <willb@redhat.com>
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'capricious/lfsr'
20
+ require 'capricious/mwc5'
21
+ require 'capricious/sample_sink'
22
+
23
+ module Capricious
24
+ module PRNG
25
+
26
+ def initialize(seed=nil, policy=MWC5, keep_stats=false)
27
+ prng_initialize(seed, policy, keep_stats)
28
+ end
29
+
30
+ def prng_initialize(seed=nil, policy=MWC5, keep_stats=false)
31
+ @prng = policy.new_with_seed(seed)
32
+ @seed = @prng.seed
33
+ @aggregate = SampleSink.new if keep_stats
34
+ end
35
+
36
+ def next
37
+ val = next_value
38
+ @aggregate << val if @aggregate
39
+ val
40
+ end
41
+
42
+ def reset(seed=nil)
43
+ @prng.reset(seed)
44
+ @aggregate = SampleSink.new if @aggregate
45
+ end
46
+
47
+ def self.included(base)
48
+ base.class_eval do
49
+ attr_reader :aggregate, :seed
50
+ end
51
+ end
52
+ end
53
+ end
@@ -47,13 +47,13 @@ module Capricious
47
47
  reset
48
48
  end
49
49
 
50
- def next
50
+ def next_i
51
51
  shift_reg
52
52
  @reg
53
53
  end
54
54
 
55
55
  def next_f
56
- self.next
56
+ next_i
57
57
  @reg.quo(@ns::MASK.to_f)
58
58
  end
59
59
 
@@ -66,7 +66,8 @@ module Capricious
66
66
  module SixtyFourBitShifter
67
67
  MASK = 0xffffffffffffffff
68
68
  SIZE = 64
69
- BITS = [64,63,61,60]
69
+ # BITS = [64,63,61,60]
70
+ BITS = [64,4,3,1]
70
71
  BITSELECT = BITS.map {|bit| "@reg[#{SIZE-bit}]"}.join("^")
71
72
 
72
73
  class_eval <<-SHIFT_REG
@@ -0,0 +1,71 @@
1
+ # capricious/mwc5.rb: 32-bit multiply-with-carry PRNG
2
+ #
3
+ # Copyright (c) 2010 Red Hat, Inc.
4
+ #
5
+ # Author: William Benton <willb@redhat.com>
6
+ # Algorithm due to George Marsaglia: http://groups.google.com/group/comp.lang.c/msg/e3c4ea1169e463ae
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ require 'capricious/lfsr'
21
+
22
+ module Capricious
23
+ class MWC5
24
+ attr_reader :seed, :seeds
25
+ def MWC5.new_with_seed(seed)
26
+ MWC5.new(seed)
27
+ end
28
+
29
+ def initialize(seed=nil)
30
+ reset(seed)
31
+ end
32
+
33
+ def reset(seed=nil)
34
+ @seed ||= (seed || Time.now.utc.to_i)
35
+ unless @seeds
36
+ seeder = LFSR.new_with_seed(@seed)
37
+ @seeds = [seeder.next_i & 0xffffffff]
38
+ 4.times do
39
+ 9.times do
40
+ # the observed lag-10 autocorrelation of the LFSRs is low
41
+ seeder.next_i
42
+ end
43
+ @seeds << (seeder.next_i & 0xffffffff)
44
+ end
45
+ end
46
+
47
+ @x,@y,@z,@w,@v = @seeds
48
+ end
49
+
50
+ def next_i
51
+ shift_ks
52
+ end
53
+
54
+ def next_f
55
+ next_i.quo(0xffffffff.to_f)
56
+ end
57
+
58
+ private
59
+ def shift_ks
60
+ # XXX: this is ugly, but it has to be to avoid coercing things into bignums
61
+ t=(@x^(@x>>7)) & 0xffffffff
62
+ @x=@y
63
+ @y=@z
64
+ @z=@w
65
+ @w=@v
66
+ @v=(@v^(@v<<6))^(t^(t<<13)) & 0xffffffff
67
+ yy = ((@y & 0x7fffffff << 1) + 1) & 0xffffffff
68
+ (yy * @v) & 0xffffffff;
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,46 @@
1
+ # capricious/poisson.rb: Poisson-distribution PRNG, with selectable source-randomness policy
2
+ #
3
+ # Copyright (c) 2010 Red Hat, Inc.
4
+ #
5
+ # Author: William Benton <willb@redhat.com>
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'capricious/generic_prng'
20
+
21
+ module Capricious
22
+ class Poisson
23
+ include PRNG
24
+
25
+ attr_reader :z
26
+
27
+ def initialize(l, seed=nil, policy=MWC5, keep_stats=false)
28
+ @z = Math.exp(-l)
29
+ prng_initialize(seed, policy, keep_stats)
30
+ end
31
+
32
+ private
33
+ # Algorithm 369, CACM, January 1970
34
+ def next_value
35
+ k = 0
36
+
37
+ t = @prng.next_f
38
+ while t > self.z
39
+ k = k + 1
40
+ t = t * @prng.next_f
41
+ end
42
+
43
+ k
44
+ end
45
+ end
46
+ end
@@ -19,13 +19,13 @@
19
19
  module Capricious
20
20
  class SampleSink
21
21
 
22
- attr_reader :min, :max, :count, :mean
22
+ attr_reader :min, :max, :count, :mean, :variance
23
23
 
24
24
  def initialize
25
25
  @min = nil
26
26
  @max = nil
27
27
  @count = 0
28
- @var = 0.0
28
+ @variance = 0.0
29
29
  @mean = 0.0
30
30
  @sum_x2 = 0.0
31
31
  end
@@ -42,7 +42,7 @@ module Capricious
42
42
  end
43
43
 
44
44
  def stddev
45
- Math::sqrt(@var)
45
+ Math::sqrt(@variance)
46
46
  end
47
47
 
48
48
  private
@@ -56,7 +56,7 @@ module Capricious
56
56
  dev = sample - @mean
57
57
  @mean += (dev / @count)
58
58
  @sum_x2 += (dev * (sample - @mean))
59
- @var = @sum_x2 / @count
59
+ @variance = @sum_x2 / @count
60
60
  end
61
61
  end
62
62
  end
@@ -16,28 +16,15 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'capricious/lfsr'
20
- require 'capricious/sample_sink'
19
+ require 'capricious/generic_prng'
21
20
 
22
21
  module Capricious
23
22
  class Uniform
24
- attr_reader :aggregate, :seed
23
+ include PRNG
25
24
 
26
- def initialize(seed=nil, policy=LFSR, keep_stats=false)
27
- @prng = policy.new_with_seed(seed)
28
- @seed = @prng.seed
29
- @aggregate = SampleSink.new if keep_stats
30
- end
31
-
32
- def next
33
- val = @prng.next_f
34
- @aggregate << val if @aggregate
35
- val
36
- end
37
-
38
- def reset(seed=nil)
39
- @prng.reset(seed)
40
- @aggregate = SampleSink.new if @aggregate
25
+ private
26
+ def next_value
27
+ @prng.next_f
41
28
  end
42
29
  end
43
30
  end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Capricious
4
+ LAMBDA = 8
5
+ SAMPLE_COUNT = 200
6
+
7
+ describe Poisson do
8
+ def generate_samples(count=SAMPLE_COUNT)
9
+ count.times {@poisson.next}
10
+ end
11
+
12
+ before(:each) do
13
+ @poisson = Capricious::Poisson.new(LAMBDA, nil, MWC5, true)
14
+ end
15
+
16
+ it "should generate distributed numbers in a Poisson distribution with lambda #{LAMBDA}, as judged by mean and variance estimates" do
17
+ generate_samples(10000)
18
+ @poisson.aggregate.mean.should be_close(LAMBDA, 0.05)
19
+ @poisson.aggregate.variance.should be_close(LAMBDA, 0.15)
20
+ end
21
+
22
+ it "should generate the same sequence given the same seed" do
23
+ @poisson2 = Poisson.new(LAMBDA, @poisson.seed, MWC5, true)
24
+ SAMPLE_COUNT.times { @poisson2.next.should == @poisson.next }
25
+ end
26
+ end
27
+ end
data/spec/uniform_spec.rb CHANGED
@@ -3,32 +3,32 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  module Capricious
4
4
  EXPECTED_MEAN = 0.5
5
5
  EXPECTED_STDDEV = 0.288675134594813
6
- SAMPLE_COUNT = 200000
6
+ SAMPLE_COUNT = 20000
7
7
 
8
8
  describe Uniform do
9
- def generate_samples(count=SAMPLE_COUNT)
9
+ def generate_samples(policy=MWC5, count=SAMPLE_COUNT)
10
+ @uniform = Capricious::Uniform.new(nil, policy, true)
10
11
  count.times {@uniform.next}
11
12
  end
12
13
 
13
- before(:each) do
14
- @uniform = Capricious::Uniform.new(nil, LFSR, true)
15
- end
16
-
17
- it "should generate numbers in the range (0,1]" do
18
- generate_samples
19
- @uniform.aggregate.max.should <= 1.0
20
- @uniform.aggregate.min.should > 0.0
21
- end
14
+ [LFSR,MWC5].each do |policy|
15
+ it "should, given policy #{policy.name}, generate numbers in the range (0,1]" do
16
+ generate_samples(policy)
17
+ @uniform.aggregate.max.should <= 1.0
18
+ @uniform.aggregate.min.should > 0.0
19
+ end
22
20
 
23
- it "should generate uniformly-distributed numbers in the range (0,1], as judged by mean and variance estimates" do
24
- generate_samples
25
- @uniform.aggregate.mean.should be_close(EXPECTED_MEAN, 0.01)
26
- @uniform.aggregate.stddev.should be_close(EXPECTED_STDDEV, 0.01)
27
- end
21
+ it "should, given policy #{policy.name}, generate uniformly-distributed numbers in the range (0,1], as judged by mean and variance estimates" do
22
+ generate_samples(policy)
23
+ @uniform.aggregate.mean.should be_close(EXPECTED_MEAN, 0.01)
24
+ @uniform.aggregate.stddev.should be_close(EXPECTED_STDDEV, 0.01)
25
+ end
28
26
 
29
- it "should generate the same sequence given the same seed" do
30
- @uniform2 = Uniform.new(@uniform.seed, LFSR, true)
31
- SAMPLE_COUNT.times { @uniform2.next.should == @uniform.next }
27
+ it "should, given policy #{policy.name}, generate the same sequence given the same seed" do
28
+ @uniform = Uniform.new(nil, policy, false)
29
+ @uniform2 = Uniform.new(nil, policy, false)
30
+ (SAMPLE_COUNT/10).times { @uniform2.next.should == @uniform.next }
31
+ end
32
32
  end
33
33
  end
34
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capricious
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Benton
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-03-11 00:00:00 -06:00
12
+ date: 2010-03-12 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,9 +40,13 @@ files:
40
40
  - Rakefile
41
41
  - VERSION
42
42
  - lib/capricious.rb
43
+ - lib/capricious/generic_prng.rb
43
44
  - lib/capricious/lfsr.rb
45
+ - lib/capricious/mwc5.rb
46
+ - lib/capricious/poisson.rb
44
47
  - lib/capricious/sample_sink.rb
45
48
  - lib/capricious/uniform.rb
49
+ - spec/poisson_spec.rb
46
50
  - spec/spec.opts
47
51
  - spec/spec_helper.rb
48
52
  - spec/uniform_spec.rb
@@ -75,5 +79,6 @@ signing_key:
75
79
  specification_version: 3
76
80
  summary: Pseudorandom number generator classes and support code
77
81
  test_files:
82
+ - spec/poisson_spec.rb
78
83
  - spec/spec_helper.rb
79
84
  - spec/uniform_spec.rb