# = brent_root_finder.rb -
# Minimization- Minimization algorithms on pure Ruby
# Copyright (C) 2010 Claudio Bustos
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# This algorith was adopted and ported into Ruby from GNU GSL library's
# brent.c [https://github.com/ampl/gsl/blob/master/roots/brent.c]. Therefore this
# file is under under the terms of the GNU General Public License.
# Research Paper - http://maths-people.anu.edu.au/~brent/pd/rpb005.pdf
#
# Brent method for finding roots

module Minimization

  # Brent's root finding method.  
  # == Usage
  #  f           = lambda {|x| x**2}
  #  root_finder = Minimization::BrentRootFinder.new(100000)
  #  min_x       = root_finder.find_root(-1000,1000, f)
  #
  class BrentRootFinder
    attr_accessor :max_iterations
    MAX_ITERATIONS_DEFAULT = 10e6
    EPSILON                = 10e-10

    def initialize(max_iterations=nil)
      @iterations = 0
      if (@max_iterations.nil?)
        @max_iterations = MAX_ITERATIONS_DEFAULT
      end
    end

    def f(x)
      return @f.call(x)
    end

    # Find root in interval (lower_bound, upper_bound) of function f
    # == Parameters:
    # * <tt>lower_bound</tt>: Lower bound of the minimization search
    # * <tt>upper_bound</tt>: Upper bound of the minimization search
    # * <tt>f</tt>: Function to find roots
    #
    def find_root(lower_bound, upper_bound, f)
      @f = f
      lower  = lower_bound
      f_upper = f(lower_bound)
      upper  = upper_bound
      f_lower = f(upper_bound)
      c  = lower
      fc = f_upper
      d  = upper - lower
      e  = d

      absolute_accuracy = EPSILON
      relative_accuracy = EPSILON

      loop do
        @iterations += 1
        if (fc.abs < f_lower.abs)
          lower  = upper
          upper  = c
          c  = lower
          f_upper = f_lower
          f_lower = fc
          fc = f_upper
        end

        tolerance = 2 * relative_accuracy * upper.abs + absolute_accuracy
        m   = 0.5 * (c - upper)

        if (m.abs <= tolerance or f_lower.abs < EPSILON or @iterations > @max_iterations)
          return upper
        end
        if (e.abs < tolerance or f_upper.abs <= f_lower.abs)
          # use bisection
          d = m
          e = d
        else 
          # use inverse cubic interpolation
          s = f_lower / f_upper
          if (lower == c)
            p = 2 * m * s
            q = 1 - s
          else
            q = f_upper / fc
            r = f_lower / fc
            p = s * (2 * m * q * (q - r) - (upper - lower) * (r - 1))
            q = (q - 1) * (r - 1) * (s - 1)
          end
          if (p > 0)
            q = -q
          else 
            p = -p
          end
          s = e
          e = d
          if (p >= 1.5 * m * q - (tolerance * q).abs or p >= (0.5 * s * q).abs)
            # interpolation failed, fall back to bisection
            d = m
            e = d
          else
            d = p / q
          end
        end
        # Update the best estimate of the root and bounds on each iteration
        lower  = upper
        f_upper = f_lower

        if (d.abs > tolerance)
          upper += d
        elsif (m > 0)
          upper += tolerance
        else
          upper -= tolerance
        end
        f_lower = f(upper)
        if ((f_lower > 0 and fc > 0) or (f_lower <= 0 and fc <= 0))
          c  = lower
          fc = f_upper
          d  = upper - lower
          e  = d
        end
      end
    end
  end
end
