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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d9c8ce05833c10354ed5a50430c8a2d23c605b8
4
- data.tar.gz: 6f3f5990f6484cd086da5226903e79bd2948a643
3
+ metadata.gz: 75a1742fcfa84e13ef7b67d0a3718676a240f00d
4
+ data.tar.gz: 49d428dd3e4f9292cd7eec5098422e119216a518
5
5
  SHA512:
6
- metadata.gz: 09fadb723ab85663042ce147028c5144b62fd80c605b601d638a00285963e7970bd4018e20bbd42404ccba2707f5c026a1e620e83e8535517b6f28e24ec8e07d
7
- data.tar.gz: 68b35d64dc97aba4466da942f77dc161eb2b3933dc5a98c77a500239edd042f6031df5c1e9f6ca5b481ad03e689cedb64bae53b508b2079a24a4b98ab317581f
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
- will_accept_connection = RationalChoice::Dimension.new(20, 10) # ten connections is always safe
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)
@@ -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 = '1.0.0'
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 false_bound[#to_f, #<=>] the lower bound, at or below which the value will be considered false
18
- # @param true_bound[#to_f, #<=>] the upper bound, at or above which the value will be considered true
19
- def initialize(false_bound, true_bound)
20
- raise DomainError, "Bounds were the same at #{false_bound}" if false_bound == true_bound
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 = [false_bound, true_bound].sort
23
- @flip_sign = [@lower, @upper].sort != [false_bound, true_bound]
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
- # will_accept_connection = Dimension.new(20, 10) # ten connections is always safe
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 !fuzzy?(value)
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
@@ -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 1.0.0 ruby lib
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 = "1.0.0"
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 = "2015-12-06"
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(100).of(10_000 / 2)
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: 1.0.0
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: 2015-12-06 00:00:00.000000000 Z
11
+ date: 2016-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard