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 self.terms = Hash.new { |hash, key| hash[key] = Term.new(key) } 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.keys.none?(&:zero?) self.alter { |nf, term| nf.terms[term.exponent-1].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 if degree < 2 return {} else extrema = self.derivative.extrema curvature_behaviour = Hash.new {|h,k|h[k]=Set.new} extrema.values.inject(&:|).sort.each_cons(2) do |start_point,end_point| curvature_behaviour[AfterextremaCurvatureMapping[extrema.find { |k,v| v.include?(start_point) }.first]] << Range.new(start_point,end_point) end end return curvature_behaviour end def degree self.terms.keys.max end def to_s terms.delete_if { |_,t| t.coefficient.zero? }.sort_by { |_,t| -t.exponent }.inject("") do |string,(_,term)| string << term.to_s end.strip 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 a = self.terms[self.degree].coefficient max_or_min = (self.degree.even? ? [1,1] : [-1,1]).map { |n| (n * a)/a.abs } extrema[MinMaxMapping[max_or_min.first]] << -1.0/0 extrema[MinMaxMapping[max_or_min.last]] << 1.0/0 return extrema end private def coefficients_till(n) coefficients = [] (0..n).each do |e| coefficients << self.terms[e].coefficient end return coefficients.reverse end protected def alter new_function = self.class.new self.terms.values.each do |term| yield new_function, term end return new_function end end