nondeterminism 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +86 -0
- data/lib/nondeterminism.rb +52 -0
- data/test/array_test.rb +18 -0
- data/test/integer_generator_test.rb +45 -0
- data/test/percent_probability_test.rb +42 -0
- data/test/probability_event_chain_test.rb +43 -0
- data/test/probability_generator_test.rb +39 -0
- data/test/range_test.rb +28 -0
- data/test/ratio_probability_test.rb +96 -0
- data/test/test_helper.rb +1 -0
- metadata +62 -0
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
Copyright 2008 James Rosen
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
Some example use cases:
|
19
|
+
|
20
|
+
|
21
|
+
# accept only half of emails
|
22
|
+
50.percent_of_the_time do
|
23
|
+
email.accept!
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# accept only half of emails and send nasty
|
28
|
+
# responses to the rest
|
29
|
+
50.percent_of_the_time do
|
30
|
+
email.accept!
|
31
|
+
end.else do
|
32
|
+
email.send_nasty_response!
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# name should be
|
37
|
+
# 'jerome' 30% of the time
|
38
|
+
# 'kevin' 40% of the time
|
39
|
+
# 'leslie' the rest of the time
|
40
|
+
|
41
|
+
with_probability(0.30) do
|
42
|
+
name = 'jerome'
|
43
|
+
end.else_with_probability(0.40) do
|
44
|
+
name = 'kevin'
|
45
|
+
end.else
|
46
|
+
name = 'leslie'
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# select a random friend from an Array:
|
51
|
+
friend = person.friends.random_element
|
52
|
+
|
53
|
+
|
54
|
+
# call foo between 5 and 8 times (inclusive):
|
55
|
+
(5..8).times do
|
56
|
+
foo
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
Using a custom random probability generator:
|
62
|
+
if you want to change the random-probability-generation mechanism, do
|
63
|
+
something like:
|
64
|
+
|
65
|
+
Nondeterminism::probability_generator = Proc.new { ... }
|
66
|
+
|
67
|
+
or
|
68
|
+
|
69
|
+
Nondeterminism::probability_generator = my_obj
|
70
|
+
|
71
|
+
just make sure my_obj responds to #call or #to_proc and only returns
|
72
|
+
numbers in [0.0, 1.0]
|
73
|
+
|
74
|
+
|
75
|
+
Using a custom random integer generator:
|
76
|
+
if you want to change the random-integer-generation mechanism, do
|
77
|
+
something like:
|
78
|
+
|
79
|
+
Nondeterminism::integer_generator = Proc.new { |low, high| ... }
|
80
|
+
|
81
|
+
or
|
82
|
+
|
83
|
+
Nondeterminism::integer_generator = Proc.new { |low, high| ... }
|
84
|
+
|
85
|
+
just make sure my_obj responds to #to_proc, the proc has #arity = 2
|
86
|
+
(or < -2), and only returns numbers in [low, high]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'nondeterminism/probability/kernel'
|
2
|
+
require 'nondeterminism/probability/numeric'
|
3
|
+
require 'nondeterminism/array'
|
4
|
+
require 'nondeterminism/range'
|
5
|
+
|
6
|
+
module Nondeterminism
|
7
|
+
|
8
|
+
DEFAULT_PROBABILITY_GENERATOR = Proc.new { Kernel.rand }
|
9
|
+
|
10
|
+
@@probability_generator = DEFAULT_PROBABILITY_GENERATOR
|
11
|
+
|
12
|
+
def self.probability_generator
|
13
|
+
@@probability_generator
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.probability_generator=(generator)
|
17
|
+
raise ArgumentError.new('generator must respond to #to_proc') unless generator.respond_to?(:to_proc)
|
18
|
+
@@probability_generator = generator.to_proc
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.random_probability
|
22
|
+
result = probability_generator.call
|
23
|
+
raise "generator (#{probability_generator}) generated invalid probability: #{result}" unless result >= 0.0 && result <= 1.0
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
DEFAULT_INTEGER_GENERATOR = Proc.new { |low, high| Kernel.rand * high + low }
|
29
|
+
|
30
|
+
@@integer_generator = DEFAULT_PROBABILITY_GENERATOR
|
31
|
+
|
32
|
+
def self.integer_generator
|
33
|
+
@@integer_generator
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.integer_generator=(generator)
|
37
|
+
raise ArgumentError.new('generator must respond to #to_proc') unless generator.respond_to?(:to_proc)
|
38
|
+
proc = generator.to_proc
|
39
|
+
raise ArgumentError.new('generator must have arity == 2 or < -2') unless proc.arity == 2 || proc.arity < -2
|
40
|
+
@@integer_generator = proc
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.random_integer(range)
|
44
|
+
self.random_integer(range.first, range.last)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.random_integer(low, high)
|
48
|
+
result = integer_generator.call(low, high).to_i
|
49
|
+
raise "generator (#{integer_generator}) generated invalid integer: #{result}" unless result >= low && result <= high
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
data/test/array_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
class ArrayTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Nondeterminism::integer_generator = Proc.new { |x, y| x }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_rand
|
11
|
+
assert_equal 'a', ['a', 'b', 'c'].rand
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_rand_when_empty
|
15
|
+
assert_nil [].rand
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
|
5
|
+
class NoToProc
|
6
|
+
end
|
7
|
+
|
8
|
+
class ToProcArityZero
|
9
|
+
def to_proc
|
10
|
+
Proc.new { nil }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ToProcArityTwoReturnsFour
|
15
|
+
def to_proc
|
16
|
+
Proc.new { |x, y| 4 }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ToProcArityTwoReturnsAverage
|
21
|
+
def to_proc
|
22
|
+
Proc.new { |x, y| ((x + y) / 2).to_i }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class IntegerGeneratorTest < Test::Unit::TestCase
|
27
|
+
|
28
|
+
def test_generator_must_define_to_proc
|
29
|
+
assert_raises(ArgumentError) { Nondeterminism::integer_generator = NoToProc.new }
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_generator_must_have_arity_2
|
33
|
+
assert_raises(ArgumentError) do
|
34
|
+
Nondeterminism::integer_generator = ToProcArityZero.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_generator_must_return_integers_in_bounds
|
39
|
+
Nondeterminism::integer_generator = ToProcArityTwoReturnsFour.new
|
40
|
+
assert_raises(RuntimeError) do
|
41
|
+
Nondeterminism::random_integer(5, 9)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
class PercentProbabilisticTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Nondeterminism::probability_generator = Proc.new { 0.5 }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_negative_percent_raises_argument_error
|
11
|
+
assert_raises(ArgumentError) { -4.percent_of_the_time { nil } }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_percent_over_100_raises_argument_error
|
15
|
+
assert_raises(ArgumentError) { 110.percent_of_the_time { nil } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_simple_percent_probability
|
19
|
+
i = 0
|
20
|
+
50.percent_of_the_time { i = 1 }
|
21
|
+
assert_equal 1, i
|
22
|
+
|
23
|
+
49.9.percent_of_the_time { i = 2 }
|
24
|
+
assert_equal 1, i
|
25
|
+
|
26
|
+
51.0001.percent_of_the_time { i = 3 }
|
27
|
+
assert_equal 3, i
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_percent_probability_with_else
|
31
|
+
i = 0
|
32
|
+
50.percent_of_the_time { i = 1 }.else { i = 2 }
|
33
|
+
assert_equal 1, i
|
34
|
+
|
35
|
+
49.9.percent_of_the_time { i = 3 }.else { i = 4 }
|
36
|
+
assert_equal 4, i
|
37
|
+
|
38
|
+
51.0001.percent_of_the_time { i = 5 }.else { i = 6 }
|
39
|
+
assert_equal 5, i
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'nondeterminism/probability/event_chain'
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class ProbabilityEventChainTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def new_chain(*args)
|
8
|
+
Nondeterminism::Probability::EventChain.new(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_initialize_probability
|
12
|
+
assert_equal 0.0, new_chain.total_probability
|
13
|
+
assert_equal 0.2, new_chain(0.2, 0.7).total_probability
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_initialize_random
|
17
|
+
assert new_chain.random_value >= 0.0
|
18
|
+
assert new_chain.random_value <= 1.0
|
19
|
+
assert_equal 0.75, new_chain(0.2, 0.75).random_value
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_completed
|
23
|
+
assert_equal false, new_chain(0.6, 0.7).completed?
|
24
|
+
assert_equal true, new_chain(0.8, 0.7).completed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_else_requires_block
|
28
|
+
assert_raises(ArgumentError) { new_chain.else }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_else_with_probability_requires_block
|
32
|
+
assert_raises(ArgumentError) { new_chain.else_with_probability(0.9) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_else_else_raises_error
|
36
|
+
assert_raises(NoMethodError) { 5.percent_of_the_time { nil }.else { nil }.else { nil } }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_else_else_with_probability_raises_error
|
40
|
+
assert_raises(NoMethodError) { 5.percent_of_the_time { nil }.else { nil }.else_with_probability(0.1) { nil } }
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
|
5
|
+
class NoToProc
|
6
|
+
end
|
7
|
+
|
8
|
+
class ToProcReturnsNegativeOne
|
9
|
+
def to_proc
|
10
|
+
Proc.new { -1 }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class ToProcReturnsFour
|
16
|
+
def to_proc
|
17
|
+
Proc.new { 4 }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ProbabilityGeneratorTest < Test::Unit::TestCase
|
22
|
+
|
23
|
+
def test_generator_must_define_to_proc
|
24
|
+
assert_raises(ArgumentError) { Nondeterminism::probability_generator = NoToProc.new }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_generator_must_return_integers_in_bounds
|
28
|
+
Nondeterminism::probability_generator = ToProcReturnsNegativeOne.new
|
29
|
+
assert_raises(RuntimeError) do
|
30
|
+
Nondeterminism::random_probability
|
31
|
+
end
|
32
|
+
|
33
|
+
Nondeterminism::probability_generator = ToProcReturnsFour.new
|
34
|
+
assert_raises(RuntimeError) do
|
35
|
+
Nondeterminism::random_probability
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/test/range_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
class RangeTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Nondeterminism::integer_generator = Proc.new { |x, y| y }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_n_times
|
11
|
+
i = 0
|
12
|
+
(6..9).times { i += 1 }
|
13
|
+
assert_equal 9, i
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_upper_bound
|
17
|
+
i = 0
|
18
|
+
(1...8).times { i += 1 }
|
19
|
+
assert_equal 7, i
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_negative_value_raises_error
|
23
|
+
Nondeterminism::integer_generator = Proc.new { |x, y| x }
|
24
|
+
i = 0
|
25
|
+
assert_raises(RuntimeError) { (-4..2).times { i += 1 } }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
|
4
|
+
class RatioProbabilisticTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Nondeterminism::probability_generator = Proc.new { 0.5 }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_negative_ratio_raises_argument_error
|
11
|
+
assert_raises(ArgumentError) { with_probability(-2.2) { nil } }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_ratio_over_one_raises_argument_error
|
15
|
+
assert_raises(ArgumentError) { with_probability(1.07) { nil } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_simple_ratio_probability
|
19
|
+
i = 0
|
20
|
+
with_probability(0.3) { i = 1 }
|
21
|
+
assert_equal 0, i
|
22
|
+
|
23
|
+
with_probability(0.5) { i = 1 }
|
24
|
+
assert_equal 1, i
|
25
|
+
|
26
|
+
with_probability(1.0) { i = 10 }
|
27
|
+
assert_equal 10, i
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_else
|
31
|
+
i = 0
|
32
|
+
with_probability(0.3) { i = 1 }.else { i = 2 }
|
33
|
+
assert_equal 2, i
|
34
|
+
|
35
|
+
with_probability(0.5) { i = 3 }.else { i = 4 }
|
36
|
+
assert_equal 3, i
|
37
|
+
|
38
|
+
with_probability(1.0) { i = 5 }.else { i = 6 }
|
39
|
+
assert_equal 5, i
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_else_with_probability
|
43
|
+
i = 0
|
44
|
+
with_probability(0.2) do
|
45
|
+
i = 1
|
46
|
+
end.else_with_probability(0.2999) do
|
47
|
+
i = 2
|
48
|
+
end.else_with_probability(0.0002) do
|
49
|
+
i = 3
|
50
|
+
end.else_with_probability(0.15) do
|
51
|
+
i = 4
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_equal 3, i
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_chain_percent_over_100_raises_argument_error
|
58
|
+
assert_raises(ArgumentError) do
|
59
|
+
with_probability(0.9) do
|
60
|
+
nil
|
61
|
+
end.else.with_probability(0.09) do
|
62
|
+
nil
|
63
|
+
end.else.with_probability(0.05) do
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_long_chain
|
70
|
+
i = 0
|
71
|
+
with_probability(0.11) do
|
72
|
+
i = 1
|
73
|
+
end.else_with_probability(0.1) do
|
74
|
+
i = 2
|
75
|
+
end.else_with_probability(0.1) do
|
76
|
+
i = 3
|
77
|
+
end.else_with_probability(0.1) do
|
78
|
+
i = 4
|
79
|
+
end.else_with_probability(0.1) do
|
80
|
+
i = 5
|
81
|
+
end.else_with_probability(0.1) do
|
82
|
+
i = 6
|
83
|
+
end.else_with_probability(0.1) do
|
84
|
+
i = 7
|
85
|
+
end.else_with_probability(0.1) do
|
86
|
+
i = 8
|
87
|
+
end.else_with_probability(0.1) do
|
88
|
+
i = 9
|
89
|
+
end.else do
|
90
|
+
i = 100000
|
91
|
+
end
|
92
|
+
|
93
|
+
assert_equal 5, i
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'nondeterminism'
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nondeterminism
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Rosen
|
8
|
+
autorequire: nondeterminism
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-30 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: james @nospam@ universal-presence.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- lib/nondeterminism.rb
|
26
|
+
- test/array_test.rb
|
27
|
+
- test/integer_generator_test.rb
|
28
|
+
- test/percent_probability_test.rb
|
29
|
+
- test/probability_event_chain_test.rb
|
30
|
+
- test/probability_generator_test.rb
|
31
|
+
- test/range_test.rb
|
32
|
+
- test/ratio_probability_test.rb
|
33
|
+
- test/test_helper.rb
|
34
|
+
- README
|
35
|
+
has_rdoc: true
|
36
|
+
homepage:
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 0.9.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 2
|
60
|
+
summary: A package for nondeterministic and probabilistic programming.
|
61
|
+
test_files: []
|
62
|
+
|