class RationalChoice::Dimension

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

Public Class Methods

new(false_at_or_below:, true_at_or_above:, random: Random.new) click to toggle source

Initializes a new Dimension to evaluate values

@param false_at_or_below[#to_f, #<=>] the lower bound, at or below which the value will be considered false @param true_at_or_above[#to_f, #<=>] the upper bound, at or above which the value will be considered true @param random the RNG, defaults to a new Random

# 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

Public Instance Methods

choose(value) click to toggle source

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

@param value[#to_f, Comparable] a value to be evaluated (must be coercible to a Float and Comparable) @return [Boolean] the chosen value based on probability and randomness

# 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) click to toggle source

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

@param value a value to be evaluated (must be comparable) @return [Boolean] whether choosing on this value will use probabilities or not

# File lib/rational_choice.rb, line 73
def fuzzy?(value)
  value > @lower && value < @upper
end