a first two-dimensional clustering algorithm

This commit is contained in:
Axel Uhl committed 2015-01-22 14:01:16 +01:00
1 parent f834e5637b
commit 95f6264ed4
5 files changed
+109 -6

No files matched your search

@@ -14,7 +14,7 @@ public class ScalablePosition implements ScalableValueWithDistance<ScalablePosit
Math.sin(position.getLatRad()));
}
public ScalablePosition(double x, double y, double z) {
private ScalablePosition(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
@@ -1,4 +1,4 @@
package com.sap.sailing.polars.windestimation.test;
package com.sap.sailing.polars.windestimation;
import static org.junit.Assert.assertEquals;
@@ -9,18 +9,31 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.ScalableWind;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.windestimation.ManeuverBasedWindEstimationTrackImpl;
import com.sap.sailing.polars.windestimation.ManeuverBasedWindEstimationTrackImpl.ManeuverClassification;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.util.kmeans.Cluster;
import com.sap.sse.util.kmeans.KMeansMappingClusterer;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -67,6 +80,28 @@ public class TestWindEstimationFromManeuversOnAFew505Races extends OnlineTracTra
Wind average = getManeuverBasedAverageWind();
assertEquals(275, average.getFrom().getDegrees(), 5.0); // wind in this race was from 075deg on average
}
@Test
public void testTwoDimensionalClustering() throws NotEnoughDataHasBeenAddedException, MalformedURLException,
IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException,
CreateModelException {
setUp("event_20110609_KielerWoch-505_Race_3");
ManeuverBasedWindEstimationTrackImpl windTrack = new ManeuverBasedWindEstimationTrackImpl(new PolarDataServiceImpl(Executors.newFixedThreadPool(4)),
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000, /* waitForLatest */ true);
final Map<Maneuver, Competitor> maneuvers = windTrack.getAllManeuvers(/* waitForLatest */ true);
final int numberOfClusters = 16;
KMeansMappingClusterer<ManeuverClassification, Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>, ScalableBearingAndScalableDouble> clusterer =
new KMeansMappingClusterer<>(numberOfClusters,
windTrack.getManeuverClassifications(maneuvers),
(mc)->new ScalableBearingAndScalableDouble(mc.getMiddleManeuverCourse(), mc.getManeuverAngleDeg()), // maps maneuver classification to cluster metric
// use an evenly distributed set of cluster seeds for clustering wind direction estimations
Stream.concat(IntStream.range(0, numberOfClusters/2).mapToObj((i)->
new Pair<>(new DegreeBearingImpl(((double) i)*360./(double) numberOfClusters/2), 45.)),
IntStream.range(0, numberOfClusters/2).mapToObj((i)->
new Pair<>(new DegreeBearingImpl(((double) i)*360./(double) numberOfClusters/2), -45.))));
final Set<Cluster<ManeuverClassification, Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>, ScalableBearingAndScalableDouble>> clusters = clusterer.getClusters();
assertEquals(8, clusters.size());
}
private Wind getManeuverBasedAverageWind() throws NotEnoughDataHasBeenAddedException {
ManeuverBasedWindEstimationTrackImpl windTrack = new ManeuverBasedWindEstimationTrackImpl(new PolarDataServiceImpl(Executors.newFixedThreadPool(4)),
@@ -454,12 +454,15 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
/**
* For each maneuver from <code>maneuvers</code>, a {@link ManeuverClassification object} is created.
*/
private Stream<ManeuverClassification> getManeuverClassifications(final Map<Maneuver, Competitor> maneuvers) {
Stream<ManeuverClassification> getManeuverClassifications(final Map<Maneuver, Competitor> maneuvers) {
return maneuvers.entrySet().stream().map((maneuverAndCompetitor)->
new ManeuverClassification(maneuverAndCompetitor.getValue(), maneuverAndCompetitor.getKey()));
}
private Map<Maneuver, Competitor> getAllManeuvers(boolean waitForLatest) {
/**
* Package scope to let test fragment access it
*/
Map<Maneuver, Competitor> getAllManeuvers(boolean waitForLatest) {
Map<Maneuver, Competitor> maneuvers = new HashMap<>();
final Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
@@ -0,0 +1,61 @@
package com.sap.sailing.polars.windestimation;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.scalablevalue.ScalableValue;
import com.sap.sse.common.scalablevalue.ScalableValueWithDistance;
/**
* Two {@link Bearings} combined in one scalable object, like a vector of two bearings. {@link #add(ScalableValue)
* Addition} works component wise, and so does {@link #multiply(double) multiplication} and {@link #divide(double)
* division}. The {@link #getDistance(Pair) distance} is calculated as the Euklidian two-dimensional distance between
* the two bearing pairs, with "degrees" as conceptual unit. For example, another object of this type that is 2 and 3 degrees
* apart from this object will have a distance of {@link Math#sqrt(double) Math.sqrt(13)}.
*
* @author Axel Uhl (D043530)
*
*/
public class ScalableBearingAndScalableDouble implements
ScalableValueWithDistance<Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>> {
private final ScalableBearing b1;
private final ScalableDouble b2;
public ScalableBearingAndScalableDouble(Bearing b1, double b2) {
this(new ScalableBearing(b1), new ScalableDouble(b2));
}
private ScalableBearingAndScalableDouble(ScalableBearing b1, ScalableDouble b2) {
super();
this.b1 = b1;
this.b2 = b2;
}
@Override
public Pair<Bearing, Double> divide(double divisor) {
return new Pair<>(b1.divide(divisor), b2.divide(divisor));
}
@Override
public Pair<ScalableBearing, ScalableDouble> getValue() {
return new Pair<>(b1, b2);
}
@Override
public double getDistance(Pair<Bearing, Double> other) {
final double b1DiffDeg = b1.divide(1).getDifferenceTo(other.getA()).getDegrees();
final double b2DiffDeg = Math.abs(b2.divide(1)-other.getB());
return Math.sqrt(b1DiffDeg*b1DiffDeg + b2DiffDeg*b2DiffDeg);
}
@Override
public ScalableBearingAndScalableDouble add(ScalableValue<Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>> t) {
return new ScalableBearingAndScalableDouble(b1.add(t.getValue().getA()), b2.add(t.getValue().getB()));
}
@Override
public ScalableBearingAndScalableDouble multiply(double factor) {
return new ScalableBearingAndScalableDouble(b1.multiply(factor), b2.multiply(factor));
}
}
@@ -115,7 +115,11 @@ public class Cluster<E, ValueType, AveragesTo, T extends ScalableValueWithDistan
@Override
public String toString() {
return "{mean: "+getMean()+", centroid: "+getCentroid()+", elements: "+elements+"}";
return "{mean: "+getMean()+", centroid: "+getCentroid()+", size: "+size()+", variance: "+getVariance()+", elements: "+elements+"}";
}
private double getVariance() {
return stream().mapToDouble((e)->getDistanceFromMean(e)*getDistanceFromMean(e)).average().getAsDouble();
}
public Stream<E> stream() {