package edu.stanford.rsl.conrad.fitting;
import java.util.Random;
import edu.stanford.rsl.conrad.utils.DoubleArrayUtil;
public class LogarithmicFunction extends Function{
/**
*
*/
private static final long serialVersionUID = 3584939480038216387L;
private double a;
private double b;
private double c;
private double d;
public LogarithmicFunction(){
numberOfParameters = 4;
}
private static double bFunction(double b, double x1, double x2, double x3, double y1, double y2, double y3){
double z = (y1-y2) / (y1-y3);
return (Math.pow(x1 - b, z-1) * (x2 - b)) - Math.pow(x3 - b, z);
}
private static double estimateA(double b, double x1, double x2, double y1, double y2){
double nominator = (y1 - y2);
double denominator = Math.log((x1 - b) / (x2 -b));
return nominator / denominator;
}
private static double estimateC(double b, double x1, double x2, double y1, double y2){
double z = y2 / y1;
double nominator = x2 - b;
double denominator = Math.pow(x1 - b, z);
return Math.pow(nominator / denominator, 1 / (1 -z));
}
private static double estimateD(double a, double b, double c, double x, double y){
return y - (a * Math.log((x - b)/ c));
}
@Override
public double evaluate(double x){
return (a * Math.log((x - b) / c)) + d;
}
private static double estimateB(double x1, double x2, double x3, double y1, double y2, double y3){
int scale = 20000;
double bestB = 0;
double minDist = Double.MAX_VALUE;
for (int i=0;i<scale;i++){
double x = ((i + 0.0) / scale * 10) - 5;
double b = bFunction(x, x1, x2, x3, y1, y2, y3);
if(Math.abs(b) < minDist){
minDist = Math.abs(b);
bestB = x;
}
}
return bestB;
}
public String toString(){
if (fittingDone){
return "y = (" + a + " * log((x - " +
b + ") / " +
c + ")) + " +
d;
} else {
return "y = (a * log((x - b) / c)) + d";
}
}
public void fitToThreePoints(double x1, double x2, double x3, double y1, double y2, double y3){
b = estimateB(x1, x2, x3, y1, y2, y3);
a = estimateA(b, x1, x2, y1, y2);
c = estimateC(b, x1, x2, y1, y2);
d = estimateD(a, b, c, x1, y1);
fittingDone = true;
}
private boolean isValidEstimate(){
boolean revan = true;
if (Double.isInfinite(a) || Double.isNaN(a)) revan = false;
if (Double.isInfinite(b) || Double.isNaN(b)) revan = false;
if (Double.isInfinite(c) || Double.isNaN(c)) revan = false;
if (Double.isInfinite(d) || Double.isNaN(d)) revan = false;
return revan;
}
public double getA() {
return a;
}
public void setA(double a) {
this.a = a;
}
public double getB() {
return b;
}
public void setB(double b) {
this.b = b;
}
public double getC() {
return c;
}
public void setC(double c) {
this.c = c;
}
public double getD() {
return d;
}
public void setD(double d) {
this.d = d;
fittingDone = true;
}
@Override
public void fitToPoints(double[] x, double [] y) {
if (x.length == 3) {
fitToThreePoints(x[0], x[1], x[2], y[0], y[1], y[2]);
} else {
Random random = new Random();
double [] stats = DoubleArrayUtil.minAndMaxOfArray(x);
double range = stats[1] - stats[0];
int tries = 20;
double a = 0;
double b = 0;
double c = 0;
double d = 0;
for (int i = 0; i < tries; i ++){
// select three random indices in the first the second and the third third
int fraction = 10;
int one = DoubleArrayUtil.findClosestIndex(stats[0] + (random.nextDouble() * (range / fraction)), x);
int two = DoubleArrayUtil.findClosestIndex(stats[0] + (range/2) +(random.nextDouble() * (range / fraction)), x);
int three = DoubleArrayUtil.findClosestIndex(stats[0] + range - (random.nextDouble() * (range / fraction)), x);
fitToThreePoints(x[one], x[two], x[three], y[one], y[two], y[three]);
if (isValidEstimate()){
a += this.a / tries;
b += this.b / tries;
c += this.c / tries;
d += this.d / tries;
} else {
// retry
i--;
}
}
this.a = a;
this.b = b;
this.c = c;
this.d = d;
fittingDone = true;
}
}
@Override
public int getMinimumNumberOfCorrespondences() {
return 3;
}
@Override
public double[] getParametersAsDoubleArray() {
return new double[]{a,b,c,d};
}
}