/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx import scala.collection.Iterable trait Fitter[T]{ def fit[A](proto:T, ds:Iterable[A], f:A=>(Double,Double)):T } /* trait Univariate[T]{ def apply(x:Double):Double //evaluate def grad(x:Double):T //define the gradient def coefs:ListMap[Symbol,Double] //--derived def toKson:String = coefs map {(k,v) => s"$k:$v"} mkString " " def fromCoefArray(coefs:Array[Double]):T = new T() def coefArray:Array[Double] = coeffs } */ //TODO add an implicit fitting function here using Chomsky decomposition and forward/backward solving. For now use external libs wrappers like drx.Math._ that wraps apache commons // object Fitter{ // implicit object FitterPolynomial extends cc.drx.Fitter[Polynomial]{ // def fit[A](proto:Polynomial, ds:Iterable[A], f:A=>(Double,Double)):Polynomial = ??? // } // } case class FirstOrderStep(a:Double, w:Double){ def apply(x:Double):Double = a*(1.0 - math.exp(-w*x)) override def toString = f"$a*(1 - e^(-$w*x))" def toKson = f"FirstOrderStep a:$a w:$w" def fit[A](ds:Iterable[A])(implicit f:A=>(Double,Double), fitter:Fitter[FirstOrderStep]):FirstOrderStep = fitter.fit(this,ds,f) //if A = (Double,Double) then use the implicit Predef.identity to pluck out x and y values } case class Exponential(b:Double, a:Double, k:Double, t:Double){ def apply(x:Double):Double = b + a*math.exp(k*(x-t)) override def toString = f"$b + $a e^($k (x-$t))" def toKson = f"Exponential: b:$b a:$a k:$k t:$t" def fit[A](ds:Iterable[A])(implicit f:A=>(Double,Double), fitter:Fitter[Exponential]):Exponential = fitter.fit(this,ds,f) //if A = (Double,Double) then use the implicit Predef.identity to pluck out x and y values } object Polynomial{ /**coeffs(0) is order 1*/ def apply(coeffs:Double*):Polynomial = Polynomial(coeffs.toArray) def order(n:Int) = Polynomial(Array.fill[Double](n+1)(1)) } case class Polynomial(coeff:Array[Double]){ def order = coeff.size-1 //def apply(x:Double) = coeff.zipWithIndex.foldLeft(0.0){case (a,(c,i)) => a + c*(math.pow(x,i))} def apply(x:Double):Double = coeff.foldLeft(0.0->1.0){case ((a,xi),c) => a + c*xi -> xi*x}._1 def toKson = "Polynomial coefs:" + (coeff mkString " ") override def toString = coeff.zipWithIndex.foldLeft(""){case (a,(c,i)) => if(i==0) f"$c" else { val exp = if(i>1) "^"+i else "" val sgn = if(c < 0) "-" else "+" f"$a $sgn ${c.abs} x$exp" } } def fit[A](ds:Iterable[A])(implicit f:A=>(Double,Double), fitter:Fitter[Polynomial]):Polynomial = fitter.fit(this,ds,f) //if A = (Double,Double) then use the implicit Predef.identity to pluck out x and y values } object Logistic{ def apply():Logistic = Logistic(k=1,m=1,b=1, q=1,a=1,n=1) //reasonably well starting conditions for fitting problems def ones = Logistic(k=1,m=1,b=1, q=1,a=1,n=1) //reasonably well starting conditions for fitting problems } case class Logistic(k:Double, //ending value: asymptote to \inf (or -\inf if b<0) m:Double, //start time: (of max derivative) b:Double, //growth rate q:Double, //pos of curve (related to apply(0)) a:Double, //starting value: lower asymptote n:Double //\nu pos of max growth ( >0 ) ){ require(n > 0) require(k >= a) private val kMinusA = k - a private val nInv = 1.0/n def apply(x:Double) = a + kMinusA / math.pow(1.0 + q * math.exp(b * (m-x)), nInv) override def toString = f"$a + $kMinusA / (1 + $q * e^($b * ($m-x)))^$nInv"; def toKson = f"Logistic k:$k m:$m b:$b q:$q a:$a n:$n" def fit[A](ds:Iterable[A])(implicit f:A=>(Double,Double), fitter:Fitter[Logistic]):Logistic = fitter.fit(this,ds,f) def shift(x:Double) = copy(m = m+x) }