class Flt::Solver::Base

Attributes

iteration[R]
reason[R]

Public Class Methods

new(*args, &blk) click to toggle source

Admitted options:

  • :context : Numerical context (e.g. Flt::Decimal::Context) Float by default

  • :default_guesses : default initial guesses

  • :tolerance : numerical tolerance

  • :equation : equation to be solved; can also be passed as a block

default_guesses: nil for no pre-guess, or one or two guesses (use array for two)

The values of any of the parameters can also be passed as arguments (not in the options Hash, if present) in any order, e.g.:

Solver::Base.new Flt::DecNum.context, tolerance: Flt::Tolerance(3, :decimals)
# File lib/solver/base.rb, line 23
def initialize(*args, &blk)
  options = Base.extract_options(*args, &blk)
  @context = options[:context] || Float.context
  @default_guesses = Array(options[:default_guesses])
  @x = @default_guesses.first
  @f = options[:equation]
  @tol = options[:tolerance] # user-requested tolerance
  @max_it = 8192
  reset
end

Protected Class Methods

extract_options(*args, &blk) click to toggle source
# File lib/solver/base.rb, line 122
def self.extract_options(*args, &blk)
  options = {}
  args.each do |arg|
    case arg
    when Hash
      options.merge! arg
    when Flt::Num::ContextBase, Flt::FloatContext
      options.merge! context: arg
    when Array, Numeric
      options.merge! default_guesses: Array(arg)
    when Proc
      options.merge! equation: arg
    when Flt::Tolerance
      options.merge! tolerance: arg
    when Base
      options.merge! solver_class: arg
    else
      raise "Invalid Argument #{arg.inspect}"
    end
  end
  options[:equation] ||= blk
  options
end

Public Instance Methods

reset() click to toggle source
# File lib/solver/base.rb, line 34
def reset
  @l_x = nil
  @fx = nil
  @l_fx = nil
  @ok = true
  @conv = false
end
root(*guesses) click to toggle source
# File lib/solver/base.rb, line 42
def root(*guesses)
  @guess = (guesses + @default_guesses).map{|g| num(g)}
  reset
  @l_x = @x = @guess.first
  @l_fx = @fx = eval_f(@x)
  @ok = true
  @conv = false

  # Minimum tolerance of the numeric type used
  @numeric_tol = Flt::Tolerance(1,:ulps) # Tolerance(@context.epsilon, :floating)

  raise "Invalid parameters" unless validate

  @reason = nil
  @iteration = 0
  # TODO: handle NaNs (stop or try to find other guess)
  while @ok && @iteration < @max_it
    next_x = step()
    @l_x = @x
    @l_fx = @fx
    @x = next_x
    @fx = eval_f(@x)
    @conv = test_conv() if @ok
    break if @conv
    @iteration += 1
  end
  @ok = false if @iteration >= @max_it # TODO: set reason
  @x

end
value() click to toggle source
# File lib/solver/base.rb, line 73
def value
  @fx
end

Protected Instance Methods

eval_f(x) click to toggle source
# File lib/solver/base.rb, line 81
def eval_f(x)
  @context.math x, &@f
end
num(v) click to toggle source
# File lib/solver/base.rb, line 85
def num(v)
  @context.Num(v)
end
test_conv() click to toggle source
# File lib/solver/base.rb, line 93
def test_conv
  #@tol.eq?(@x, @l_x) || @tol.eq?(@fx, @l_fx) || zero?
  # puts "test #{@x} #{@fx}"
  # if @tol.eq?(@fx, @l_fx) && !(@tol.eq?(@x, @l_x) || zero?)
  #   puts "---> v=#{@tol.relative_to_many(:max, @fx, @l_fx)} #{@fx} #{@l_fx} x=#{@x}"
  # end

  # zero? || @x==@l_x || @fx == @l_fx
  #@numeric_tol.eq?(@x, @l_x) || zero? || @numeric_tol.eq?(@fx, @l_fx)

  # TODO : use symbols for reason
  if zero?
    @reason = "Zero found #{@fx.inspect} @ #{@x.inspect}"
  elsif @numeric_tol.eq?(@x, @l_x)
    @reason = "Critical point" # Sign Reversal (@fx != @l_fx) or vertical tangent / asymptote
  elsif @numeric_tol.eq?(@fx, @l_fx)
    @reason = "Flat" # flat function
  end
  !@reason.nil?

  # TODO: try to get out of flat; if @x==@l_x try to find other point?;

  #zero?
end
validate() click to toggle source
# File lib/solver/base.rb, line 118
def validate
  true
end
zero?() click to toggle source
# File lib/solver/base.rb, line 89
def zero?
  @tol.zero?(@fx)
end