capricious 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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