Cleaned up least squares implementations and tests

This commit is contained in:
Frederik Petersen committed 2015-02-17 13:03:19 +01:00
1 parent 88aee2ad03
commit 1576b00aef
21 files changed
+387 -607

No files matched your search

@@ -32,4 +32,6 @@ public interface PolarSheetGenerationSettings extends Serializable {
boolean areDefault();
int getNumberOfLeadingCompetitorsToInclude();
}
@@ -11,7 +11,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false);
false, 0);
}
public static PolarSheetGenerationSettings createBackendPolarSettings() {
@@ -26,7 +26,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
}
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false);
false, 1);
}
private static final long serialVersionUID = 2731616509404813790L;
@@ -42,6 +42,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
private boolean useOnlyEstimationForWindDirection;
private WindSpeedSteppingWithMaxDistance windStepping;
private boolean splitByWindGauges;
private int numberOfLeadingCompetitorsToInclude;
//GWT
PolarSheetGenerationSettingsImpl() {};
@@ -51,7 +52,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
boolean useOnlyWindGaugesForWindSpeed, boolean shouldRemoveOutliers,
double outlierDetectionNeighboorhoodRadius, double outlierMinimumNeighboorhoodPct,
boolean useOnlyEstimationForWindDirection, WindSpeedSteppingWithMaxDistance windStepping,
boolean splitByWindGauges) {
boolean splitByWindGauges, int numberOfLeadingCompetitorsToInclude) {
this.minimumDataCountPerGraph = minimumDataCountPerGraph;
this.minimumWindConfidence = minimumWindConfidence;
this.minimumDataCountPerAngle = minimumDataCountPerAngle;
@@ -64,6 +65,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection;
this.windStepping = windStepping;
this.splitByWindGauges = splitByWindGauges;
this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude;
}
@Override
@@ -206,5 +208,10 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
return true;
}
@Override
public int getNumberOfLeadingCompetitorsToInclude() {
return numberOfLeadingCompetitorsToInclude;
}
}
@@ -69,7 +69,7 @@ public class PolarSheetGenerationServiceTest {
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
0.05, false, windStepping, false);
0.05, false, windStepping, false, 0);
List<RegattaAndRaceIdentifier> idList = new ArrayList<RegattaAndRaceIdentifier>();
idList.add(new RegattaNameAndRaceName("IrgendeineRegatta", "IrgendeinRennen"));
@@ -162,7 +162,7 @@ public class PolarSheetGenerationSettingsDialogComponent implements SettingsDial
useOnlyWindGaugesForWindSpeedBox.getValue(), shouldRemoveOutliersBox.getValue(),
outlierRadiusBox.getValue(), outlierNeighborhoodPctBox.getValue(),
useOnlyEstimationForWindDirectionBox.getValue(), windSteppingBox.getStepping(windSteppingMaxDistanceBox
.getValue()), splitByWindGaugesBox.getValue());
.getValue()), splitByWindGaugesBox.getValue(), 0 /*TODO*/);
}
@Override
@@ -9,9 +9,12 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -26,12 +29,15 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -51,11 +57,26 @@ public class PolarDataMinerTest {
private static final int MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED = 30;
private static final double EPSILON = 1E-4;
public static PolarSheetGenerationSettings createTestPolarSettings() {
List<Double> levelList = new ArrayList<Double>();
for (double levelValue = 0.5; levelValue < 35; levelValue = levelValue + 0.5) {
levelList.add(levelValue);
}
double[] levels = new double[levelList.size()];
int i=0;
for (Double level : levelList) {
levels[i++] = level;
}
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false, 3);
}
@Test
public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException,
NotEnoughDataHasBeenAddedException {
PolarDataMiner miner = new PolarDataMiner();
PolarDataMiner miner = new PolarDataMiner(createTestPolarSettings());
BoatClass mockedBoatClass = mock(BoatClass.class);
@@ -150,22 +171,26 @@ public class PolarDataMinerTest {
track.add(fix);
}
}
RaceDefinition mockedRaceDefinition = createMockedRaceDefinition();
when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass);
when(trackedRace.getRace()).thenReturn(mockedRaceDefinition);
for (Competitor competitor : fixesPerCompetitor.keySet()) {
MarkPassing markpassing = createMockedStartMarkPassing();
when(trackedRace.getMarkPassing(eq(competitor), any(Waypoint.class))).thenReturn(markpassing);
}
Calendar cal = Calendar.getInstance();
cal.set(2014, 4, 3, 12, 00);
TimePoint startOfRace = new MillisecondsTimePoint(cal.getTime());
cal.set(2014, 4, 3, 15, 00);
TimePoint endOfRace = new MillisecondsTimePoint(cal.getTime());
Waypoint finishWaypoint = mock(Waypoint.class);
createWayPoint(fixesPerCompetitor, trackedRace, endOfRace, finishWaypoint);
Waypoint startWaypoint = mock(Waypoint.class);
createWayPoint(fixesPerCompetitor, trackedRace, startOfRace, startWaypoint);
RaceDefinition mockedRaceDefinition = createMockedRaceDefinition(startWaypoint, finishWaypoint);
when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass);
when(trackedRace.getRace()).thenReturn(mockedRaceDefinition);
for (Competitor competitor : fixesPerCompetitor.keySet()) {
MarkPassing markpassing = createMockedStartMarkPassing();
when(trackedRace.getMarkPassing(eq(competitor), eq(startWaypoint))).thenReturn(markpassing);
}
when(trackedRace.getStartOfRace()).thenReturn(startOfRace);
when(trackedRace.getEndOfRace()).thenReturn(endOfRace);
@@ -183,6 +208,21 @@ public class PolarDataMinerTest {
return trackedRace;
}
private void createWayPoint(Map<Competitor, Set<GPSFixMoving>> fixesPerCompetitor, TrackedRace trackedRace,
TimePoint endOfRace, Waypoint waypoint) {
List<MarkPassing> markPassings = new ArrayList<MarkPassing>();
Iterator<Competitor> competitorIterator = fixesPerCompetitor.keySet().iterator();
while (competitorIterator.hasNext()) {
MarkPassing markPassing = mock(MarkPassing.class);
when(markPassing.getTimePoint()).thenReturn(endOfRace);
Competitor competitor = competitorIterator.next();
when(markPassing.getCompetitor()).thenReturn(competitor);
when(trackedRace.getMarkPassing(competitor, waypoint)).thenReturn(markPassing);
markPassings.add(markPassing);
}
when(trackedRace.getMarkPassingsInOrder(waypoint)).thenReturn(markPassings);
}
private MarkPassing createMockedStartMarkPassing() {
Calendar cal = Calendar.getInstance();
cal.set(2014, 4, 3, 12, 15);
@@ -193,9 +233,11 @@ public class PolarDataMinerTest {
return passing;
}
private RaceDefinition createMockedRaceDefinition() {
private RaceDefinition createMockedRaceDefinition(Waypoint startWaypoint, Waypoint finishWaypoint) {
RaceDefinition raceDefinition = mock(RaceDefinition.class);
Course mockedCourse = createMockedCourse();
when(mockedCourse.getLastWaypoint()).thenReturn(finishWaypoint);
when(mockedCourse.getFirstWaypoint()).thenReturn(startWaypoint);
when(raceDefinition.getCourse()).thenReturn(mockedCourse);
BoatClass mockedBoatClass = mock(BoatClass.class);
when(mockedBoatClass.getManeuverDegreeAngleThreshold()).thenReturn(20.0);
@@ -1,52 +0,0 @@
package com.sap.sailing.polars.regression.test;
import static org.hamcrest.Matchers.closeTo;
import static org.junit.Assert.assertThat;
import java.util.Random;
import org.junit.Test;
import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression;
public class AnyOrderLeastSquaresRegressionTest {
private static final double ERROR = 0.01;
@Test
public void testNonIncrementalRegression() {
AnyOrderLeastSquaresRegression regression = new AnyOrderLeastSquaresRegression(3);
Random random = new Random(1);
for (int i = 0; i < 100000; i++) {
double x = random.nextDouble() * 40;
regression.addData(function(x) + (random.nextDouble() - 0.5), x);
}
long startTime = System.nanoTime();
double[] coeffs = regression.getCoefficiants();
System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns");
startTime = System.nanoTime();
coeffs = regression.getCoefficiants();
System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns");
double x = random.nextDouble() * 40;
regression.addData(function(x) + (random.nextDouble() - 0.5), x);
startTime = System.nanoTime() ;
coeffs = regression.getCoefficiants();
System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns");
assertThat(coeffs[0], closeTo(-2.1309, ERROR));
assertThat(coeffs[1], closeTo(1.5213, ERROR));
assertThat(coeffs[2], closeTo(-0.0373, ERROR));
assertThat(coeffs[3], closeTo(0.0003, ERROR));
}
private double function(double x) {
return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309;
}
}
@@ -0,0 +1,74 @@
package com.sap.sailing.polars.regression.test;
import static org.hamcrest.Matchers.closeTo;
import static org.junit.Assert.assertThat;
import java.util.Random;
import org.junit.Test;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
public class IncrementalLeastSquaresTest {
private static final double ERROR = 0.01;
@Test
public void testIncrementalRegression() throws NotEnoughDataHasBeenAddedException {
IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(3);
long startTime = System.nanoTime();
Random random = new Random(1);
for (int i = 0; i < 100000; i++) {
double x = random.nextDouble() * 40;
regression.addData(x, function(x) + (random.nextDouble() - 0.5));
}
System.out.println("Adding Data " + (System.nanoTime() - startTime) + "ns");
startTime = System.nanoTime();
double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients();
System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns");
startTime = System.nanoTime();
coeffs = regression.getOrCreatePolynomialFunction().getCoefficients();
System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns");
double x = random.nextDouble() * 40;
regression.addData(x, function(x) + (random.nextDouble() - 0.5));
startTime = System.nanoTime() ;
coeffs = regression.getOrCreatePolynomialFunction().getCoefficients();
System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns");
assertThat(coeffs[0], closeTo(-2.1309, ERROR));
assertThat(coeffs[1], closeTo(1.5213, ERROR));
assertThat(coeffs[2], closeTo(-0.0373, ERROR));
assertThat(coeffs[3], closeTo(0.0003, ERROR));
}
private double function(double x) {
return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309;
}
@Test
public void testIncrementalRegressionWithWebsiteExampleData() throws NotEnoughDataHasBeenAddedException {
IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(4);
regression.addData(-1, -1);
regression.addData(0, 3);
regression.addData(1, 2.5);
regression.addData(2, 5);
regression.addData(3, 4);
regression.addData(5, 2);
regression.addData(7, 5);
regression.addData(9, 4);
double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients();
assertThat(coeffs[0], closeTo(2.686, ERROR));
assertThat(coeffs[1], closeTo(2.301, ERROR));
assertThat(coeffs[2], closeTo(-1.233, ERROR));
assertThat(coeffs[3], closeTo(0.2169 , ERROR));
assertThat(coeffs[4], closeTo(-0.01182 , ERROR));
}
}
@@ -7,149 +7,139 @@ import static org.junit.Assert.assertTrue;
import org.hamcrest.number.IsCloseTo;
import org.junit.Test;
import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
public class IncrementalLinearRegressionTest {
public class IncrementalLinearRegressionOrderOneTest {
private static double EPSILON = 1E-12;
private static double VAGUE_EPSILON = 0.1;
private IncrementalLinearRegressionProcessor createRegressionProcessor() {
IncrementalLinearRegressionProcessor processor = new IncrementalLeastSquaresProcessor();
return processor;
}
@Test
public void testRegressionWithTwoDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
double x1 = 3;
double y1 = 7;
processor.addMeasuredPoint(x1, y1);
leastSquares.addData(x1, y1);
double x2 = 6;
double y2 = 14;
processor.addMeasuredPoint(x2, y2);
leastSquares.addData(x2, y2);
double y = processor.getEstimatedY(x1);
double y = leastSquares.getOrCreatePolynomialFunction().value(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
y = leastSquares.getOrCreatePolynomialFunction().value(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
y = processor.getEstimatedY(4.5);
y = leastSquares.getOrCreatePolynomialFunction().value(4.5);
assertThat(y, new IsCloseTo(10.5, EPSILON));
}
@Test
public void testRegressionWithMultipleDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
for (int i = -50; i < 51; i++) {
processor.addMeasuredPoint(i, i);
leastSquares.addData(i, i);
}
double y = processor.getEstimatedY(-2);
double y = leastSquares.getOrCreatePolynomialFunction().value(-2);
assertThat(y, new IsCloseTo(-2, EPSILON));
// 2 Points off the line
processor.addMeasuredPoint(-8, -3);
processor.addMeasuredPoint(8, -3);
leastSquares.addData(-8, -3);
leastSquares.addData(8, -3);
y = processor.getEstimatedY(-2);
y = leastSquares.getOrCreatePolynomialFunction().value(-2);
assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON));
y = processor.getEstimatedY(20);
y = leastSquares.getOrCreatePolynomialFunction().value(20);
assertThat(y, new IsCloseTo(20, VAGUE_EPSILON));
}
@Test
public void testRegressionWithABigAmountOfDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
for (int i = -1000; i < 1000; i++) {
// For every even number + 1, else - 1
double y = i % 2 == 0 ? i + 1 : i - 1;
processor.addMeasuredPoint(i, y);
leastSquares.addData(i, y);
}
double y = processor.getEstimatedY(-2);
double y = leastSquares.getOrCreatePolynomialFunction().value(-2);
assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON));
y = processor.getEstimatedY(20);
y = leastSquares.getOrCreatePolynomialFunction().value(20);
assertThat(y, new IsCloseTo(20, VAGUE_EPSILON));
}
@Test
public void testRegressionWithZeroValueData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
double x1 = 0;
double y1 = 0;
processor.addMeasuredPoint(x1, y1);
leastSquares.addData(x1, y1);
double x2 = 5;
double y2 = 0;
processor.addMeasuredPoint(x2, y2);
leastSquares.addData(x2, y2);
double y = processor.getEstimatedY(x1);
double y = leastSquares.getOrCreatePolynomialFunction().value(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
y = leastSquares.getOrCreatePolynomialFunction().value(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
}
@Test
public void testRegressionWithNegativeData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
double x1 = -5;
double y1 = -5;
processor.addMeasuredPoint(x1, y1);
leastSquares.addData(x1, y1);
double x2 = 5;
double y2 = 5;
processor.addMeasuredPoint(x2, y2);
leastSquares.addData(x2, y2);
double y = processor.getEstimatedY(x1);
double y = leastSquares.getOrCreatePolynomialFunction().value(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
y = leastSquares.getOrCreatePolynomialFunction().value(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
y = processor.getEstimatedY(0);
y = leastSquares.getOrCreatePolynomialFunction().value(0);
assertThat(y, new IsCloseTo(0, EPSILON));
}
@Test
public void testRegressionWithSmallValuedData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
double x1 = 5E-9;
double y1 = 5E-9;
for (int i = 0; i < 1000; i++) {
double yTimesI = y1 * i;
double xTimesI = x1 * i;
processor.addMeasuredPoint(xTimesI, yTimesI);
if (i > 0) {
double y = processor.getEstimatedY(xTimesI);
assertThat(y, new IsCloseTo(yTimesI, EPSILON));
}
leastSquares.addData(xTimesI, yTimesI);
}
// Add data that is off the line but offsetting each other
processor.addMeasuredPoint(-x1, y1);
processor.addMeasuredPoint(x1, -y1);
leastSquares.addData(-x1, y1);
leastSquares.addData(x1, -y1);
double y = processor.getEstimatedY(0);
double y = leastSquares.getOrCreatePolynomialFunction().value(0);
assertThat(y, new IsCloseTo(0, EPSILON));
}
@Test(expected = NotEnoughDataHasBeenAddedException.class)
public void testNoDataExceptionThrowing() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
processor.getEstimatedY(15);
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
leastSquares.getOrCreatePolynomialFunction().value(15);
}
/**
@@ -159,13 +149,13 @@ public class IncrementalLinearRegressionTest {
*/
@Test
public void testRegressionWithOneDataPoint() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
double x1 = 3;
double y1 = 5;
processor.addMeasuredPoint(x1, y1);
leastSquares.addData(x1, y1);
double y = processor.getEstimatedY(20);
double y = leastSquares.getOrCreatePolynomialFunction().value(20);
assertThat(y, is(y1));
}
@@ -196,37 +186,37 @@ public class IncrementalLinearRegressionTest {
@Test
public void testNorris() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
for (int i = 0; i < data.length; i++) {
regression.addMeasuredPoint(data[i][1], data[i][0]);
leastSquares.addData(data[i][1], data[i][0]);
}
// Tests against certified values from
// http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat
assertThat(regression.getSlope(), new IsCloseTo(1.00211681802045, EPSILON));
assertThat(regression.getIntercept(), new IsCloseTo(-0.262323073774029, EPSILON));
assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1], new IsCloseTo(1.00211681802045, EPSILON));
assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[0], new IsCloseTo(-0.262323073774029, EPSILON));
// ------------ End certified data tests
assertThat(regression.getEstimatedY(0), new IsCloseTo(-0.262323073774029, EPSILON));
assertThat(regression.getEstimatedY(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON));
assertThat(leastSquares.getOrCreatePolynomialFunction().value(0), new IsCloseTo(-0.262323073774029, EPSILON));
assertThat(leastSquares.getOrCreatePolynomialFunction().value(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON));
}
@Test
public void testPerfect() {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
public void testPerfect() throws NotEnoughDataHasBeenAddedException {
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
int n = 100;
for (int i = 0; i < n; i++) {
regression.addMeasuredPoint(((double) i) / (n - 1), i);
leastSquares.addData(((double) i) / (n - 1), i);
}
assertTrue(regression.getSlope() > 0);
assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] > 0);
}
@Test
public void testPerfectNegative() {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
public void testPerfectNegative() throws NotEnoughDataHasBeenAddedException {
IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1);
int n = 100;
for (int i = 0; i < n; i++) {
regression.addMeasuredPoint(-((double) i) / (n - 1), i);
leastSquares.addData(-((double) i) / (n - 1), i);
}
assertTrue(regression.getSlope() < 0);
assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] < 0);
}
@@ -1,43 +0,0 @@
package com.sap.sailing.polars.regression.test;
import static org.hamcrest.Matchers.closeTo;
import static org.junit.Assert.assertThat;
import java.util.Random;
import org.junit.Test;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.regression.OnlineMultiVariateRegression;
import com.sap.sailing.polars.regression.impl.OnlineMultiVariateRegressionImpl;
public class OnlineMultiVariateLinearRegressionTest {
private static double EPSILON = 1;
@Test
public void testRegressionWithPolarData() throws NotEnoughDataHasBeenAddedException {
OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(3);
Random random = new Random(1);
for (int i = 0; i < 10000; i++) {
double x = random.nextDouble() * 80 - 40;
double[] xArray = {x, x*x, x*x*x};
regression.addData(function(x, random), xArray);
}
double[] xArray = {5, 25, 125};
double estimated = regression.estimateY(xArray);
assertThat(estimated, closeTo(function(5, random), EPSILON));
}
private double function(double x) {
return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309;
}
private double function(double x, Random random) {
return function(x) + random.nextGaussian() * 5;
}
}
@@ -81,7 +81,7 @@ public class PolarSheetGenerationTest {
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
0.05, false, windStepping, false);
0.05, false, windStepping, false, 0);
TimePoint startTime = new MillisecondsTimePoint(9);
TimePoint endTime = new MillisecondsTimePoint(80);
@@ -121,7 +121,7 @@ public class PolarSheetGenerationTest {
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 10, 0, false, true, 5,
0.05, false, windStepping, false);
0.05, false, windStepping, false, 0);
PolarSheetHistogramBuilder builder = new PolarSheetHistogramBuilder(settings);
@@ -21,7 +21,8 @@ Require-Bundle:
com.sap.sse.datamining.shared;bundle-version="1.0.0",
com.sap.sse.common,
com.sap.sse,
com.sap.sse.shared.android
com.sap.sse.shared.android,
org.apache.commons.math;bundle-version="2.1.0"
Import-Package:
com.sap.sailing.util,
com.sap.sse.common
@@ -5,19 +5,21 @@ import java.util.Map;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
import com.sap.sse.common.Util.Pair;
public class CubicSpeedRegressions {
private final Map<Pair<LegType, Tack>, AnyOrderLeastSquaresRegression> regressions;
private final Map<Pair<LegType, Tack>, IncrementalLeastSquares> regressions;
public CubicSpeedRegressions() {
regressions = new HashMap<>();
for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) {
for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) {
regressions.put(new Pair<LegType, Tack>(legType, tack),
new AnyOrderLeastSquaresRegression(3));
new IncrementalAnyOrderLeastSquaresImpl(3));
}
}
}
@@ -39,17 +41,19 @@ public class CubicSpeedRegressions {
legType = LegType.UPWIND;
tack = Tack.STARBOARD;
}
AnyOrderLeastSquaresRegression regression = regressions.get(new Pair<LegType, Tack>(legType, tack));
regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots());
IncrementalLeastSquares leastSquares = regressions.get(new Pair<LegType, Tack>(legType, tack));
leastSquares.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots());
}
public double estimateSpeedInKnots(LegType legType, Tack tack, double windSpeedInKnots) {
double[] coeffs = regressions.get(new Pair<LegType, Tack>(legType, tack)).getCoefficiants();
double resultSum = 0;
for (int i = 0; i < coeffs.length; i++) {
resultSum = resultSum + coeffs[i] * Math.pow(windSpeedInKnots, i);
double result;
try {
result = regressions.get(new Pair<LegType, Tack>(legType, tack)).getOrCreatePolynomialFunction().value(windSpeedInKnots);
} catch (NotEnoughDataHasBeenAddedException e) {
//FIXME try to come up with something smarter
result = 0;
}
return resultSum;
return result;
}
}
@@ -56,7 +56,11 @@ public class PolarDataMiner implements PolarSheetAnalyzer {
private IncrementalRegressionProcessor incrementalRegressionProcessor;
public PolarDataMiner() {
backendPolarSheetGenerationSettings = PolarSheetGenerationSettingsImpl.createBackendPolarSettings();
this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings());
}
public PolarDataMiner(PolarSheetGenerationSettings backendPolarSettings) {
backendPolarSheetGenerationSettings = backendPolarSettings;
try {
setUpWorkflow();
} catch (ClassCastException | NoSuchMethodException | SecurityException e) {
@@ -81,7 +85,7 @@ public class PolarDataMiner implements PolarSheetAnalyzer {
Collection<Processor<GPSFixMovingWithPolarContext, ?>> filteringResultReceivers = Arrays.asList(groupingProcessor);
Processor<GPSFixMovingWithPolarContext, GPSFixMovingWithPolarContext> filteringProcessor = new ParallelFilteringProcessor<GPSFixMovingWithPolarContext>(
GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(1));
GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(backendPolarSheetGenerationSettings.getNumberOfLeadingCompetitorsToInclude()));
Collection<Processor<GPSFixMovingWithPolarContext, ?>> enrichingResultReceivers = Arrays
.asList(filteringProcessor);
@@ -0,0 +1,31 @@
package com.sap.sailing.polars.regression;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
/**
* Support incremental regression linear in one variable but potentially of higher order.
*
* @author Frederik Petersen (D054528)
*
*/
public interface IncrementalLeastSquares {
/**
* Adds data to the least squares regression. This only updates the internal matrix and vector and will not trigger the
* actual least square solving. See {@link #getOrCreatePolynomialFunction()}.
* @param x
* @param y
*/
public abstract void addData(double x, double y);
/**
* The polynomial function is cached and will be returned immediately if no data has been added since the last call.
* Otherwise the calculation is performed and the polynomial function is returned. It has a complexity of O(order) where
* order is the requested order of the polynomial.
*
* @return polynomial function estimating the data. Can be constant when only one data point has been added.
* @throws NotEnoughDataHasBeenAddedException Will be returned if no data has been added to the regression.
*/
public abstract PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException;
}
@@ -1,17 +0,0 @@
package com.sap.sailing.polars.regression;
public interface IncrementalLinearRegressionProcessor {
double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException;
void addMeasuredPoint(double x, double y);
double getSlope();
double getIntercept();
int getDataCount();
double getMeanOfY() throws NotEnoughDataHasBeenAddedException;
}
@@ -1,26 +0,0 @@
package com.sap.sailing.polars.regression;
public interface OnlineMultiVariateRegression {
/**
* Adds data to the regression. This will change the weights.
*
* @param y
* @param x
* length of the array needs to be the dimension of the regression (depending on the implementation, the
* dimension could be configured in the constructor), else IllegalArgumentsException will be thrown
*/
void addData(double y, double[] x);
/**
* After adding some data with {@link #addData(double, double[])} this method can be used to estimate y for any
* x-vector.
*
* @param x
* length of the array needs to be the dimension of the regression (depending on the implementation, the
* dimension could be configured in the constructor), else IllegalArgumentsException will be thrown
* @return
*/
double estimateY(double[] x);
}
@@ -1,152 +0,0 @@
package com.sap.sailing.polars.regression.impl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* Thread safe implementation of the Least Squares regression for any order of result polynomial.
*
* {@link #addData(double, double)} will incrementally update the simultaneous equations needed for the calculation of
* the result coefficients.
*
* {@link #getCoefficiants()} will do the solving of the equations, if new data has been added since the last
* calculation.
*
* @author D054528
*
*/
public class AnyOrderLeastSquaresRegression {
private final double aMatrix[][];
private final double bMatrix[];
private final double term[];
private final int nOrder;
private boolean needsCoeffUpdate = true;
private double[] coeffs;
private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("LeastSquaresLock", true);
/**
*
* @param nOrder order of the polynomial you want to fit over the data.
*/
public AnyOrderLeastSquaresRegression(int nOrder) {
this.nOrder = nOrder;
aMatrix = new double[nOrder + 1][nOrder + 1];
bMatrix = new double[nOrder + 1];
term = new double[nOrder + 1];
}
/**
* Add data to the regression. This will NOT trigger a calculation of the coefficiants, but only update the matrices
* that form the simultaneous equations.
*
* Use {@link #getCoefficiants()} to retrieve the coefficiants of the fit.
*
* @param yValue
* @param xValue
*/
public void addData(double yValue, double xValue) {
LockUtil.lockForWrite(lock);
try {
needsCoeffUpdate = true;
// sum the y values
bMatrix[0] += yValue;
// sum the x power values
double xpower = 1;
for (int j = 0; j < nOrder + 1; j++) {
term[j] = xpower;
aMatrix[0][j] += xpower;
xpower = xpower * xValue;
}
// now set up the rest of rows in the matrix - multiplying each row by each term
for (int j = 1; j < nOrder + 1; j++) {
bMatrix[j] += yValue * term[j];
for (int k = 0; k < bMatrix.length; k++) {
aMatrix[j][k] += term[j] * term[k];
}
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
/**
* If the coefficiants have been requested before, and not data was added, the coefficiants will simply be returned.
* If data was added since the last time this method was called, the calculation will take place.
*
* The calculation is standard gaussian solving of the simultaneous equations. It has the complexity (nOrder + 1)^3
*
* @return Coefficiants a[] of the polynomial function. Sorted by power => a[0]*x^0 to a[n]*x^n.
*/
public double[] getCoefficiants() {
if (needsCoeffUpdate) {
double[] coeffs = gauss();
LockUtil.lockForWrite(lock);
try {
this.coeffs = coeffs;
needsCoeffUpdate = false;
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
LockUtil.lockForRead(lock);
double[] result;
try {
result = this.coeffs;
} finally {
LockUtil.unlockAfterRead(lock);
}
return result;
}
/**
* a standard gaussian technique for / solving simultaneous equations of the form: |A| = |B| * |C| where we / know
* the values of |A| and |B|, and we are solving for the coefficients / in |C|
*/
private double[] gauss() {
double aCopy[][] = new double[aMatrix.length][aMatrix[0].length];
double bCopy[] = new double[bMatrix.length];
double pivot;
double mult;
double top;
int n = bCopy.length;
double coef[] = new double[n];
// copy over the array values - inplace solution changes values
LockUtil.lockForRead(lock);
try {
for (int i = 0; i < aMatrix.length; i++) {
for (int j = 0; j < aMatrix[i].length; j++) {
aCopy[i][j] = aMatrix[i][j];
}
bCopy[i] = bMatrix[i];
}
} finally {
LockUtil.unlockAfterRead(lock);
}
for (int j = 0; j < (n - 1); j++) {
pivot = aCopy[j][j];
for (int i = j + 1; i < n; i++) {
mult = aCopy[i][j] / pivot;
for (int k = j + 1; k < n; k++) {
aCopy[i][k] = aCopy[i][k] - mult * aCopy[j][k];
}
bCopy[i] = bCopy[i] - mult * bCopy[j];
}
}
coef[n - 1] = bCopy[n - 1] / aCopy[n - 1][n - 1];
for (int i = n - 2; i >= 0; i--) {
top = bCopy[i];
for (int k = i + 1; k < n; k++) {
top = top - aCopy[i][k] * coef[k];
}
coef[i] = top / aCopy[i][i];
}
return coef;
}
}
@@ -0,0 +1,133 @@
package com.sap.sailing.polars.regression.impl;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math.linear.LUDecompositionImpl;
import org.apache.commons.math.linear.MatrixUtils;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealVector;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* This class implements the incremental polynomial regression approach described here:
*
* <a href="http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/">
* http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/</a>
*
*
* @author Frederik Petersen D054528
*
*/
public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSquares {
private double[][] matrixOfXSums;
private double[] vectorOfXYMultSums;
private int polynomialOrder;
private AtomicLong numberOfPointsAdded = new AtomicLong(0);
private AtomicBoolean functionNeedsUpdate = new AtomicBoolean(true);
private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresLock",
false);
private final NamedReentrantReadWriteLock cacheLock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresCacheLock",
true);
private PolynomialFunction cachedFunction;
public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) {
this.polynomialOrder = polynomialOrder;
matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1];
vectorOfXYMultSums = new double[polynomialOrder + 1];
}
@Override
public void addData(double x, double y) {
LockUtil.lockForWrite(lock);
try {
numberOfPointsAdded.incrementAndGet();
functionNeedsUpdate.set(true);
for (int i = 0; i <= polynomialOrder; i++) {
vectorOfXYMultSums[i] += y * Math.pow(x, i);
for (int j = 0; j <= polynomialOrder; j++) {
if (i == 0 && j == 0) {
matrixOfXSums[i][j] += 1;
} else {
matrixOfXSums[i][j] += Math.pow(x, i+j);
}
}
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
@Override
public PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException {
PolynomialFunction resultFunction;
if (!functionNeedsUpdate.get()) {
LockUtil.lockForRead(cacheLock);
try {
resultFunction = new PolynomialFunction(cachedFunction.getCoefficients());
} finally {
LockUtil.unlockAfterRead(cacheLock);
}
} else {
if (numberOfPointsAdded.get() < 1) {
throw new NotEnoughDataHasBeenAddedException("No points have been added to the regression yet.");
}
if (numberOfPointsAdded.get() == 1) {
resultFunction = createConstantFunctionForOneDataPoint();
} else {
resultFunction = createEstimatingPolynomialFunction();
}
LockUtil.lockForWrite(cacheLock);
try {
cachedFunction = new PolynomialFunction(resultFunction.getCoefficients());
} finally {
LockUtil.unlockAfterWrite(cacheLock);
}
functionNeedsUpdate.set(false);
}
return resultFunction;
}
private PolynomialFunction createEstimatingPolynomialFunction() {
PolynomialFunction resultFunction;
RealMatrix matrixOfXSumsCopy;
RealVector vectorOfXYMultSumsCopy;
LockUtil.lockForRead(lock);
try {
matrixOfXSumsCopy = MatrixUtils.createRealMatrix(matrixOfXSums);
vectorOfXYMultSumsCopy = MatrixUtils.createRealVector(vectorOfXYMultSums);
} finally {
LockUtil.unlockAfterRead(lock);
}
RealMatrix inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse();
RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy);
resultFunction = new PolynomialFunction(coeffs.toArray());
return resultFunction;
}
private PolynomialFunction createConstantFunctionForOneDataPoint() {
LockUtil.lockForRead(lock);
double[] coeff = new double[1];
try {
coeff[0] = vectorOfXYMultSums[0];
} finally {
LockUtil.unlockAfterRead(lock);
}
return new PolynomialFunction(coeff);
}
}
@@ -1,130 +0,0 @@
package com.sap.sailing.polars.regression.impl;
import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
/**
* Allows a linear regression of a 2 dimensional and growing dataset. It has a complexity of O(1)
*
* The algorithm and variable naming is similar to the <a
* href="https://de.wikipedia.org/wiki/Methode_der_kleinsten_Quadrate#Lineare_Modellfunktion">german wikipedia
* article</a>
*
* The difference is that some parts of the equation are memorized as fields and updated incrementally.
*
* @author Frederik Petersen (D054528)
*
*/
public class IncrementalLeastSquaresProcessor implements IncrementalLinearRegressionProcessor {
private double alpha1 = 0;
private double alpha0 = 0;
private double partA = 0;
private double partB = 0;
private double meanOfX = 0;
private double meanOfY = 0;
private int numberOfPointsAdded = 0;
@Override
public double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException {
if (numberOfPointsAdded < 1) {
throw new NotEnoughDataHasBeenAddedException();
}
double result;
if (numberOfPointsAdded < 2) {
// Return the y value of the only added data point.
result = meanOfY;
} else {
result = alpha0 + alpha1 * x;
}
return result;
}
@Override
public void addMeasuredPoint(double x, double y) {
numberOfPointsAdded++;
calculatePartA(x, y);
calculatePartB(x, y);
addToArithmeticMeanOfX(x);
addToArithmeticMeanOfY(y);
calculateFactor1();
calculateFactor2();
}
private void calculateFactor2() {
alpha0 = meanOfY - alpha1 * meanOfX;
}
private void calculateFactor1() {
double numerator = calculateNumeratorOfFactor1();
double denominator = calculateDenominatorOfFactor1();
if (denominator == 0) {
alpha1 = 0;
} else {
alpha1 = numerator / denominator;
}
}
private double calculateNumeratorOfFactor1() {
return partA - numberOfPointsAdded * meanOfX * meanOfY;
}
private double calculateDenominatorOfFactor1() {
return partB - numberOfPointsAdded * meanOfX * meanOfX;
}
private void addToArithmeticMeanOfY(double y) {
meanOfY = addToMean(y, meanOfY);
}
private void addToArithmeticMeanOfX(double x) {
meanOfX = addToMean(x, meanOfX);
}
private double addToMean(double z, double oldMean) {
return oldMean + (z - oldMean) / numberOfPointsAdded;
}
private void calculatePartB(double x, double y) {
partB = partB + x * x;
}
private void calculatePartA(double x, double y) {
partA = partA + x * y;
}
@Override
public double getSlope() {
return alpha1;
}
@Override
public double getIntercept() {
return alpha0;
}
@Override
public int getDataCount() {
return numberOfPointsAdded;
}
@Override
public double getMeanOfY() throws NotEnoughDataHasBeenAddedException {
if (numberOfPointsAdded < 1) {
throw new NotEnoughDataHasBeenAddedException();
}
return meanOfY;
}
}
@@ -1,90 +0,0 @@
package com.sap.sailing.polars.regression.impl;
import com.sap.sailing.polars.regression.OnlineMultiVariateRegression;
/**
* Allows online regression for multivariate linear functions as explained in:
*
* http://people.cs.pitt.edu/~milos/courses/cs2750-Spring03/lectures/class6.pdf (especially: Page 8+9)
*
* @author Frederik Petersen (D054528)
*
*/
public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegression {
private double[] weights;
private int numberOfAddedPoints = 0;
private final double[] means;
private final double[] sumsOfSquaresOfDifferencesFromMean;
private final double[] variances;
public OnlineMultiVariateRegressionImpl(int numberOfDimensions) {
weights = initializeDoubleArray(numberOfDimensions + 1);
means = initializeDoubleArray(numberOfDimensions);
sumsOfSquaresOfDifferencesFromMean = initializeDoubleArray(numberOfDimensions);
variances = initializeDoubleArray(numberOfDimensions);
}
private double[] initializeDoubleArray(int arrayLength) {
double[] weights = new double[arrayLength];
for (int i = 0; i < weights.length; i++) {
weights[i] = 0;
}
return weights;
}
@Override
public void addData(double y, double[] x) {
checkNumberOfDimensions(x);
numberOfAddedPoints++;
double[] newWeights = initializeDoubleArray(weights.length);
double alpha = 0.005;
newWeights[0] = weights[0] + alpha * (y - estimateY(x));
for (int i = 1; i < weights.length; i++) {
incrementallyUpdateVariances(x, i);
double normalizedInput = normalizeInput(x, i - 1);
newWeights[i] = weights[i] + (y - estimateY(x)) * Math.pow(alpha, i + 1) * x[i - 1];// normalizedInput;
}
weights = newWeights;
}
private double normalizeInput(double[] x, int dimensionIndex) {
double normalizedInput;
if (variances[dimensionIndex] > 0) {
normalizedInput = (x[dimensionIndex] - means[dimensionIndex]) / Math.sqrt(variances[dimensionIndex]);
} else {
normalizedInput = x[dimensionIndex];
}
return normalizedInput;
}
private void incrementallyUpdateVariances(double[] x, int i) {
//http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Incremental_algorithm
double oldMean = means[i-1];
means[i-1] = means[i-1] + ((x[i-1] - means[i-1]) / numberOfAddedPoints);
sumsOfSquaresOfDifferencesFromMean[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] + (x[i-1] - oldMean) * (x[i-1] - means[i-1]);
variances[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] / numberOfAddedPoints ;
}
@Override
public double estimateY(double[] x) {
checkNumberOfDimensions(x);
double resultSum = weights[0];
for (int i = 1; i < weights.length; i++) {
double normalizedInput = normalizeInput(x, i - 1);
resultSum = resultSum + (weights[i] * x[i - 1]);
}
return resultSum;
}
private void checkNumberOfDimensions(double[] x) {
if (x.length + 1 != weights.length) {
throw new IllegalArgumentException("Dimension of x needs to be the same as specified in constructor.");
}
}
}
@@ -47,6 +47,7 @@
<unit id="org.apache.commons.io.source" version="2.2.0.v201405211200"/>
<unit id="org.apache.commons.lang" version="2.6.0.v201404270220"/>
<unit id="org.apache.commons.lang.source" version="2.6.0.v201404270220"/>
<unit id="org.apache.commons.math" version="2.1.0.v201105210652"/>
<unit id="org.jdom" version="1.1.1.v201101151400"/>
<unit id="org.jdom.source" version="1.1.1.v201101151400"/>
<unit id="javax.validation" version="1.0.0.GA_v201205091237"/>
@@ -103,6 +104,7 @@
<plugin id="org.apache.commons.lang.source"/>
<plugin id="org.apache.commons.logging"/>
<plugin id="org.apache.commons.logging.source"/>
<plugin id="org.apache.commons.math"/>
<plugin id="org.apache.felix.gogo.command"/>
<plugin id="org.apache.felix.gogo.command.source"/>
<plugin id="org.apache.felix.gogo.runtime"/>