require 'set' require_relative 'term' require_relative 'core_ext/math' class Polynomial MinMaxMapping = { 1.0 => :max, -1.0 => :min } AfterextremaCurvatureMapping = { max: :right, min: :left } NegPosMinMaxExtremumMapping = {[1.0,-1.0] => :max,[-1.0,1.0] => :min} attr_accessor :terms def self.parse(string) polynomial = self.new string.split(/(?=[-+])/).each do |term| parsed = Term.parse(term) polynomial.terms[parsed.exponent].coefficient += parsed.coefficient end return polynomial end def initialize(*args) self.terms = Hash.new { |hash, key| hash[key] = Term.new(key) } unless args.empty? args.reverse.each.with_index do |coefficient,exponent| self.terms[exponent].coefficient += coefficient end end end def calculate(x) self.terms.values.inject(0.0) do |acc,t| acc + (t.coefficient.to_f * (x**t.exponent)) end end def derivative new_function = self.alter do |nf, term| nf.terms[term.exponent - 1].coefficient += term.exponent * term.coefficient end new_function.terms.reject! { |_,t| t.coefficient == 0 } return new_function end def roots if !terms.empty? and terms.keys.none?(&:zero?) self.alter { |nf, term| nf.terms[term.exponent-self.lt.exponent].coefficient = term.coefficient }.roots << 0.0 else case self.degree when 1 Set[-self.terms[0].coefficient / self.terms[1].coefficient] when 2 Math.roots_of_quadratic_function(*coefficients_till(2)) when 3 Math.roots_of_cubic_function(*coefficients_till(3)) when 4 Math.roots_of_quartic_function(*coefficients_till(4)) else Set[] end end end def local_extrema derivative = self.derivative max_min_extremum = Hash.new { |hash,key| hash[key] = Set.new } possible_extrema = derivative.roots.sort unless possible_extrema.empty? samples = ([possible_extrema.first - 1] + possible_extrema.sort + [possible_extrema.last + 1]).each_cons(2).map do |before,after| (before + after)/2 end possible_extrema.zip(samples.each_cons(2)).each do |pe,(after,before)| yafter = derivative.calculate(after) ybefore = derivative.calculate(before) kind_of_extremum = NegPosMinMaxExtremumMapping[[yafter/yafter.abs,ybefore/ybefore.abs]] max_min_extremum[kind_of_extremum] << pe if kind_of_extremum end end return max_min_extremum end def curvature_behaviour hash = Hash.new {|h,k|h[k]=Set.new} return hash if self.degree > 5 extrema = self.derivative.extrema all_extremas = extrema.values.inject(Set[],&:|).sort all_extremas.each_cons(2).map { |s,e| Range.new(s,e) }.group_by do |range| kind_of_curvature = AfterextremaCurvatureMapping[extrema.find { |k,v| v.include?(range.begin) }.first] end end def degree self.terms.keys.max || 0 end def to_s terms.sort_by { |_,t| -t.exponent }.inject("") do |string,(_,term)| string << term.to_s unless term.coefficient.zero? && !term.exponent.zero? string end.strip.sub(/\A\+\s/, '') end def ==(other) delete_zeros = proc{ |_,t| t.coefficient.zero? } self.terms.delete_if(&delete_zeros) == other.terms.delete_if(&delete_zeros) end def extrema extrema = local_extrema unless self.degree == 0 a = self.gt.coefficient max_or_min = (self.degree.even? ? [1.0,1.0] : [-1.0,1.0]).map { |n| (n * a)/a.abs } extrema[MinMaxMapping[max_or_min.first]] << -1.0/0 extrema[MinMaxMapping[max_or_min.last]] << 1.0/0 end return extrema end def gt self.terms[self.degree] end def lt self.terms[self.terms.min_by{ |_,t| t.exponent}.first] end def coefficients_till(n) coefficients = [] (0..n).each do |e| coefficients << self.terms[e].coefficient end return coefficients.reverse end private :coefficients_till def alter new_function = self.class.new self.terms.values.each do |term| yield new_function, term end return new_function end protected :alter end