rational_choice 1.0.0 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/lib/rational_choice.rb +15 -13
- data/rational_choice.gemspec +3 -3
- data/spec/rational_choice/dimension_spec.rb +13 -13
- data/spec/rational_choice/many_dimensions_spec.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75a1742fcfa84e13ef7b67d0a3718676a240f00d
|
4
|
+
data.tar.gz: 49d428dd3e4f9292cd7eec5098422e119216a518
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d51bbedb0f841e8d14a397a74794a46642806d522dcae325d47e2fde225d4f704908162f0f512af163ff02618e34b682ce37bb63788cadef32e40b59c26fa98
|
7
|
+
data.tar.gz: 1d5cddf81c1fcb5d8086dda58b01213a7e85ef4c956fe6efd6d322f44d095c6cfe4327b48357bb5c3acee9d43cf880695128b9ee337d92477a9de6feac945608
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# rational_choice
|
2
2
|
|
3
|
+
[](https://travis-ci.org/WeTransfer/rational_choice)
|
4
|
+
|
3
5
|
Makes a fuzzy logic choice based on a continuum of values. For when a "yes or no" is not-quite-good-enough.
|
4
6
|
|
5
7
|
## Choice on one dimension of values
|
@@ -9,7 +11,8 @@ and you know that it can take about 20 maximum. When you decide whether to send
|
|
9
11
|
number 17 to it, you want to take a little margin and only send that connection sometimes, to
|
10
12
|
balance the choices - so you want to use a softer bound (a bit of a fuzzy logic).
|
11
13
|
|
12
|
-
|
14
|
+
# ten connections is always safe
|
15
|
+
will_accept_connection = RationalChoice::Dimension.new(false_at_or_below: 20, true_at_or_above:10)
|
13
16
|
if will_accept_connection.choose(server.current_connection_count + 1) # will give you a fuzzy choice
|
14
17
|
server.accept(new_client)
|
15
18
|
else
|
@@ -24,8 +27,8 @@ or above the upper bound will always choose `true`.
|
|
24
27
|
|
25
28
|
Useful to give rational choices on multiple values, averaged together.
|
26
29
|
|
27
|
-
num_clients = Dimension.new(200, 100)
|
28
|
-
bandwidth = Dimension.new(2048, 1024)
|
30
|
+
num_clients = Dimension.new(false_at_or_below: 200, true_at_or_above: 100)
|
31
|
+
bandwidth = Dimension.new(false_at_or_below:2048, true_at_or_above: 1024)
|
29
32
|
has_capacity = ManyDimensions.new(num_clients, bandwidth)
|
30
33
|
|
31
34
|
will_accept_connection = has_capacity.choose(current_client_count, current_bandwidth)
|
data/lib/rational_choice.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Tiny fuzzy-logic gate for making choices based on a continuum of permitted values
|
2
2
|
# as opposed to a hard condition.
|
3
3
|
module RationalChoice
|
4
|
-
VERSION = '
|
4
|
+
VERSION = '2.0.0'
|
5
5
|
|
6
6
|
# Gets raised when a multidimensional choice has to be made with a wrong number
|
7
7
|
# of values versus the number of dimensions
|
@@ -14,13 +14,13 @@ module RationalChoice
|
|
14
14
|
class Dimension
|
15
15
|
# Initializes a new Dimension to evaluate values
|
16
16
|
#
|
17
|
-
# @param
|
18
|
-
# @param
|
19
|
-
def initialize(
|
20
|
-
raise DomainError, "Bounds were the same at #{
|
17
|
+
# @param false_at_or_below[#to_f, #<=>] the lower bound, at or below which the value will be considered false
|
18
|
+
# @param true_at_or_above[#to_f, #<=>] the upper bound, at or above which the value will be considered true
|
19
|
+
def initialize(false_at_or_below: , true_at_or_above:)
|
20
|
+
raise DomainError, "Bounds were the same at #{false_at_or_below}" if false_at_or_below == true_at_or_above
|
21
21
|
|
22
|
-
@lower, @upper = [
|
23
|
-
@flip_sign = [@lower, @upper].sort != [
|
22
|
+
@lower, @upper = [false_at_or_below, true_at_or_above].sort
|
23
|
+
@flip_sign = [@lower, @upper].sort != [false_at_or_below, true_at_or_above]
|
24
24
|
end
|
25
25
|
|
26
26
|
# Evaluate a value against the given false and true bound.
|
@@ -32,7 +32,7 @@ module RationalChoice
|
|
32
32
|
# of the value being true, based on a linear interpolation.
|
33
33
|
# For example:
|
34
34
|
#
|
35
|
-
# d = Dimension.new(0, 1)
|
35
|
+
# d = Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
36
36
|
# d.choose(0) # => false
|
37
37
|
# d.choose(1) # => true
|
38
38
|
# d.choose(0.5) #=> will be `true` in 50% of the cases (probability of 0.5)
|
@@ -43,15 +43,14 @@ module RationalChoice
|
|
43
43
|
# number 17 to it, you want to take a little margin and only send that connection sometimes, to
|
44
44
|
# balance the choices - so you want to use a softer bound (a bit of a fuzzy logic).
|
45
45
|
#
|
46
|
-
#
|
46
|
+
# # 10 connactions is doable, 20 connections means contention
|
47
|
+
# will_accept_connection = Dimension.new(false_at_or_below: 20, true_at_or_above: 10)
|
47
48
|
# will_accept_connection.choose(server.current_connection_count + 1) # will give you a fuzzy choice
|
48
49
|
#
|
49
50
|
# @param value[#to_f, Comparable] a value to be evaluated (must be coercible to a Float and Comparable)
|
50
51
|
# @return [Boolean] the chosen value based on probability and randomness
|
51
52
|
def choose(value)
|
52
|
-
choice = if
|
53
|
-
value >= @upper
|
54
|
-
else
|
53
|
+
choice = if fuzzy?(value)
|
55
54
|
# Interpolate the probability of the value being true
|
56
55
|
delta = @upper.to_f - @lower.to_f
|
57
56
|
v = (value - @lower).to_f
|
@@ -59,6 +58,9 @@ module RationalChoice
|
|
59
58
|
|
60
59
|
r = Random.new # To override in tests if needed
|
61
60
|
r.rand < t
|
61
|
+
else
|
62
|
+
# just seen where it is (below or above)
|
63
|
+
value >= @upper
|
62
64
|
end
|
63
65
|
choice ^ @flip_sign
|
64
66
|
end
|
@@ -104,7 +106,7 @@ module RationalChoice
|
|
104
106
|
evaluations = values.zip(@dimensions).map { |(v, d)| d.choose(v) }
|
105
107
|
num_truthy_choices = evaluations.select{|e| e}.length
|
106
108
|
|
107
|
-
Dimension.new(0, evaluations.length).choose(num_truthy_choices)
|
109
|
+
Dimension.new(false_at_or_below: 0, true_at_or_above: evaluations.length).choose(num_truthy_choices)
|
108
110
|
end
|
109
111
|
end
|
110
112
|
end
|
data/rational_choice.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: rational_choice
|
5
|
+
# stub: rational_choice 2.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rational_choice"
|
9
|
-
s.version = "
|
9
|
+
s.version = "2.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Julik Tarkhanov"]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2016-02-07"
|
15
15
|
s.description = "Fuzzy logic gate"
|
16
16
|
s.email = "me@julik.nl"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -4,35 +4,35 @@ describe 'RationalChoice::Dimension' do
|
|
4
4
|
describe '.new' do
|
5
5
|
it 'raises a DomainError if the bounds are the same' do
|
6
6
|
expect {
|
7
|
-
RationalChoice::Dimension.new(2,2)
|
7
|
+
RationalChoice::Dimension.new(false_at_or_below: 2, true_at_or_above: 2)
|
8
8
|
}.to raise_error(/Bounds were the same at 2/)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
describe 'with values at or beyound the thresholds' do
|
13
13
|
it 'always evaluates to true for values above upper threshold' do
|
14
|
-
d = RationalChoice::Dimension.new(0, 1)
|
14
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
15
15
|
10_000.times do
|
16
16
|
expect(d.choose(1.1)).to eq(true)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'always evaluates to true for values at upper threshold' do
|
21
|
-
d = RationalChoice::Dimension.new(0, 1)
|
21
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
22
22
|
10_000.times do
|
23
23
|
expect(d.choose(1)).to eq(true)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'always evaluates to false for values at lower threshold' do
|
28
|
-
d = RationalChoice::Dimension.new(0, 1)
|
28
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
29
29
|
10_000.times do
|
30
30
|
expect(d.choose(0)).to eq(false)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'always evaluates to false for values below lower threshold' do
|
35
|
-
d = RationalChoice::Dimension.new(0, 1)
|
35
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
36
36
|
10_000.times do
|
37
37
|
expect(d.choose(-0.0001)).to eq(false)
|
38
38
|
end
|
@@ -41,7 +41,7 @@ describe 'RationalChoice::Dimension' do
|
|
41
41
|
|
42
42
|
describe 'with values between thresholds creates a sensible choice distribution' do
|
43
43
|
it 'for 0.5 on a continuum from 0 to 1' do
|
44
|
-
d = RationalChoice::Dimension.new(0, 1)
|
44
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
45
45
|
expect(d).to be_fuzzy(0.5)
|
46
46
|
|
47
47
|
trues = 0
|
@@ -53,7 +53,7 @@ describe 'RationalChoice::Dimension' do
|
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'for 0.1 on a continuum from 0 to 1' do
|
56
|
-
d = RationalChoice::Dimension.new(0, 1)
|
56
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
57
57
|
expect(d).to be_fuzzy(0.1)
|
58
58
|
|
59
59
|
trues = 0
|
@@ -65,7 +65,7 @@ describe 'RationalChoice::Dimension' do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'for 0.5 on a continuum from 1 to 0' do
|
68
|
-
d = RationalChoice::Dimension.new(1, 0)
|
68
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 1, true_at_or_above: 0)
|
69
69
|
expect(d).to be_fuzzy(0.5)
|
70
70
|
|
71
71
|
falses = 0
|
@@ -76,7 +76,7 @@ describe 'RationalChoice::Dimension' do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'for 0.1 on a continuum from 1 to 0' do
|
79
|
-
d = RationalChoice::Dimension.new(1, 0)
|
79
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 1, true_at_or_above: 0)
|
80
80
|
expect(d).to be_fuzzy(0.1)
|
81
81
|
|
82
82
|
trues = 0
|
@@ -88,7 +88,7 @@ describe 'RationalChoice::Dimension' do
|
|
88
88
|
end
|
89
89
|
|
90
90
|
it 'for -0.5 on a continuum from -1 to 0' do
|
91
|
-
d = RationalChoice::Dimension.new(-1, 0)
|
91
|
+
d = RationalChoice::Dimension.new(false_at_or_below: -1, true_at_or_above: 0)
|
92
92
|
expect(d).to be_fuzzy(-0.5)
|
93
93
|
|
94
94
|
trues = 0
|
@@ -99,7 +99,7 @@ describe 'RationalChoice::Dimension' do
|
|
99
99
|
end
|
100
100
|
|
101
101
|
it 'for -0.1 on a continuum from -1 to 0' do
|
102
|
-
d = RationalChoice::Dimension.new(-1, 0)
|
102
|
+
d = RationalChoice::Dimension.new(false_at_or_below: -1, true_at_or_above: 0)
|
103
103
|
expect(d).to be_fuzzy(-0.1)
|
104
104
|
|
105
105
|
trues = 0
|
@@ -111,7 +111,7 @@ describe 'RationalChoice::Dimension' do
|
|
111
111
|
end
|
112
112
|
|
113
113
|
it 'for -0.5 on a continuum from 0 to -1' do
|
114
|
-
d = RationalChoice::Dimension.new(0, -1)
|
114
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: -1)
|
115
115
|
expect(d).to be_fuzzy(-0.5)
|
116
116
|
|
117
117
|
trues = 0
|
@@ -122,7 +122,7 @@ describe 'RationalChoice::Dimension' do
|
|
122
122
|
end
|
123
123
|
|
124
124
|
it 'for -0.1 on a continuum from 0 to -1' do
|
125
|
-
d = RationalChoice::Dimension.new(0, -1)
|
125
|
+
d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: -1)
|
126
126
|
expect(d).to be_fuzzy(-0.1)
|
127
127
|
|
128
128
|
trues = 0
|
@@ -18,7 +18,7 @@ describe 'RationalChoice::ManyDimensions' do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
let(:md) {
|
21
|
-
one_to_zero = RationalChoice::Dimension.new(0, 1)
|
21
|
+
one_to_zero = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
|
22
22
|
RationalChoice::ManyDimensions.new(one_to_zero, one_to_zero, one_to_zero)
|
23
23
|
}
|
24
24
|
|
@@ -47,7 +47,7 @@ describe 'RationalChoice::ManyDimensions' do
|
|
47
47
|
it 'creates a very uniform distribution of values with random values across the board' do
|
48
48
|
truthy = 0
|
49
49
|
10_000.times { truthy += 1 if md.choose(rand, rand, rand) } # default rand() is 0..1
|
50
|
-
expect(truthy).to be_within(
|
50
|
+
expect(truthy).to be_within(200).of(10_000 / 2)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rational_choice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: yard
|