diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java index 9b4805cb0a5..e5a2c4f6de0 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java @@ -32,4 +32,6 @@ public interface PolarSheetGenerationSettings extends Serializable { boolean areDefault(); + int getNumberOfLeadingCompetitorsToInclude(); + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java index d1975070b25..8f1ee656f11 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java @@ -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; + } + } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index f0fc52e1ea1..9dd9fc9cd5f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -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 idList = new ArrayList(); idList.add(new RegattaNameAndRaceName("IrgendeineRegatta", "IrgendeinRennen")); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java index ec09f5a8c49..02a89c6fa47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java @@ -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 diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java index f42c269a5c4..5c600eab0cb 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java @@ -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 levelList = new ArrayList(); + 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> fixesPerCompetitor, TrackedRace trackedRace, + TimePoint endOfRace, Waypoint waypoint) { + List markPassings = new ArrayList(); + Iterator 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); diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java deleted file mode 100644 index 0e7a728084b..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java +++ /dev/null @@ -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; - } - - - - -} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java new file mode 100644 index 00000000000..f0172f92d17 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java @@ -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)); + } + + + + +} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java similarity index 55% rename from java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java rename to java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java index 05823d6064f..b8bf40b0297 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java @@ -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); } diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java deleted file mode 100644 index 7ca7885dde3..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java +++ /dev/null @@ -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; - } - -} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java index 42b953c8aa9..3c1dfb68f7c 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java @@ -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); diff --git a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index 5b89143f5c8..77466719b0d 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -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 diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java index 3909b11201b..2e15631fc45 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -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, AnyOrderLeastSquaresRegression> regressions; + private final Map, 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), - 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)); - regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + IncrementalLeastSquares leastSquares = regressions.get(new Pair(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)).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)).getOrCreatePolynomialFunction().value(windSpeedInKnots); + } catch (NotEnoughDataHasBeenAddedException e) { + //FIXME try to come up with something smarter + result = 0; } - return resultSum; + return result; } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 95312f7740e..3ab5dd6958c 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -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> filteringResultReceivers = Arrays.asList(groupingProcessor); Processor filteringProcessor = new ParallelFilteringProcessor( - GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(1)); + GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(backendPolarSheetGenerationSettings.getNumberOfLeadingCompetitorsToInclude())); Collection> enrichingResultReceivers = Arrays .asList(filteringProcessor); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java new file mode 100644 index 00000000000..57e3e4aca88 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java @@ -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; + +} \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java deleted file mode 100644 index d4babfb0573..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java +++ /dev/null @@ -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; - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java deleted file mode 100644 index 822a0229505..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java +++ /dev/null @@ -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); - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java deleted file mode 100644 index 2c31df3bb44..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java +++ /dev/null @@ -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; - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java new file mode 100644 index 00000000000..06ac9410c4e --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -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: + * + * + * http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/ + * + * + * @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); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java deleted file mode 100644 index 64b99193c44..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java +++ /dev/null @@ -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 german wikipedia - * article - * - * 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; - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java deleted file mode 100644 index fc2698548cf..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ /dev/null @@ -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."); - } - } - -} diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index fc3bc7a06be..e57a3255ffb 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -47,6 +47,7 @@ + @@ -103,6 +104,7 @@ +