Class: RationalChoice::Dimension

Inherits:
Object
  • Object
show all
Defined in:
lib/rational_choice.rb

Overview

Represents a fuzzy choice on a single dimension (one real number)

Instance Method Summary collapse

Constructor Details

#initialize(false_at_or_below:, true_at_or_above:, random: Random.new) ⇒ Dimension

Initializes a new Dimension to evaluate values

Parameters:

  • false_at_or_below (#to_f, #<=>)

    the lower bound, at or below which the value will be considered false

  • true_at_or_above (#to_f, #<=>)

    the upper bound, at or above which the value will be considered true

  • random (Random) (defaults to: Random.new)

    the RNG, defaults to a new Random

Raises:



20
21
22
23
24
25
26
# File 'lib/rational_choice.rb', line 20

def initialize(false_at_or_below:, true_at_or_above:, random: Random.new)
  raise DomainError, "Bounds were the same at #{false_at_or_below}" if false_at_or_below == true_at_or_above

  @random = random
  @lower, @upper = [false_at_or_below, true_at_or_above].sort
  @flip_sign = [@lower, @upper].sort != [false_at_or_below, true_at_or_above]
end

Instance Method Details

#choose(value) ⇒ Boolean

Evaluate a value against the given false and true bound.

If the value is less than or equal to the false bound, the method will return ‘false`. If the value is larger than or equal to the true bound, the method will return ’true’.

If the value is between the two bounds, the method will first determine the probability of the value being true, based on a linear interpolation. For example:

d = Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
d.choose(0) # => false
d.choose(1) # => true
d.choose(0.5) #=> will be `true` in 50% of the cases (probability of 0.5)
d.choose(0.1) #=> will be `true` in 10% of the cases (probability of 0.1)

Primary use is for things like load balancing. Imagine you have a server handling 14 connections, and you know that it can take about 20 maximum. When you decide whether to send the connection number 17 to it, you want to take a little margin and only send that connection sometimes, to balance the choices - so you want to use a softer bound (a bit of a fuzzy logic).

# 10 connactions is doable, 20 connections means contention
will_accept_connection = Dimension.new(false_at_or_below: 20, true_at_or_above: 10)
will_accept_connection.choose(server.current_connection_count + 1) # will give you a fuzzy choice

Parameters:

  • value (#to_f, Comparable)

    a value to be evaluated (must be coercible to a Float and Comparable)

Returns:

  • (Boolean)

    the chosen value based on probability and randomness



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rational_choice.rb', line 54

def choose(value)
  choice = if fuzzy?(value)
    # Interpolate the probability of the value being true
    delta = @upper.to_f - @lower.to_f
    v = (value - @lower).to_f
    t = (v / delta)
    @random.rand < t
  else
    # just seen where it is (below or above)
    value >= @upper
  end
  choice ^ @flip_sign
end

#fuzzy?(value) ⇒ Boolean

Tells whether the evaluation will use the probabilities or not (whether the given value is within the range where probability evaluation will take place).

Parameters:

  • value (Comparable)

    a value to be evaluated (must be comparable)

Returns:

  • (Boolean)

    whether choosing on this value will use probabilities or not



73
74
75
# File 'lib/rational_choice.rb', line 73

def fuzzy?(value)
  value > @lower && value < @upper
end