rational_choice 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/WeTransfer/rational_choice.svg?branch=master)](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
|