Bugfixed and improved maneuver graph

This commit is contained in:
Vladislav Chumak committed 2018-09-09 20:36:26 +02:00
1 parent 2685ef282e
commit 14ec6f00ca
31 files changed
+604 -197

No files matched your search

@@ -157,7 +157,7 @@ public class SpeedRegressionPerAngleClusterProcessor implements
}
}
}
long avgFixesCount = allFixesCount / numberOfWindSpeeds;
long avgFixesCount = numberOfWindSpeeds == 0 ? 0 : allFixesCount / numberOfWindSpeeds;
long fixCountOverall = 0;
if (numberOfWindSpeeds < 2 || allFixesCount < 10) {
throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions");
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -0,0 +1 @@
bin/
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.windestimation.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,28 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Wind estimation test
Bundle-SymbolicName: com.sap.sailing.windestimation.test
Fragment-Host: com.sap.sailing.windestimation
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.osgi.framework;version="1.5.0"
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.osgi;bundle-version="3.10.1",
org.json.simple;bundle-version="1.1.0",
org.mongodb.mongo-java-driver;bundle-version="2.13.0",
org.apache.httpcomponents.httpclient;bundle-version="[4.2.5,4.3.0)",
org.apache.httpcomponents.httpcore;bundle-version="[4.0.0,4.3.0)",
com.sap.sse;bundle-version="1.0.0",
com.sap.sse.common;bundle-version="1.0.0",
com.sap.sailing.domain;bundle-version="1.0.0",
com.sap.sailing.domain.shared.android,
com.sap.sailing.domain.common;bundle-version="1.0.0",
com.sap.sailing.server.gateway.serialization;bundle-version="1.0.0",
com.sap.sailing.server.gateway.serialization.shared.android;bundle-version="1.0.0",
com.sap.sailing.polars,
org.apache.commons.io,
com.sap.sse.replication,
org.junit;bundle-version="4.8.2",
slf4j.api
Automatic-Module-Name: com.sap.sailing.velum.resultimport.test
@@ -0,0 +1,5 @@
source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.windestimation.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
</project>
@@ -0,0 +1,103 @@
package com.sap.sailing.windestimation.maneuvergraph.maneuvernode;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class WindRangeCalculationTest {
private static final double TOLERANCE = 0.0001;
@Test
public void testWithinRangeCalculation() {
WindRangeForManeuverNode range = new WindRangeForManeuverNode(20, 10);
assertEquals(range.isWindCourseWithinRange(20), true);
assertEquals(range.isWindCourseWithinRange(25), true);
assertEquals(range.isWindCourseWithinRange(30), true);
assertEquals(range.isWindCourseWithinRange(19), false);
assertEquals(range.isWindCourseWithinRange(200), false);
assertEquals(range.isWindCourseWithinRange(31), false);
range = new WindRangeForManeuverNode(350, 20);
assertEquals(range.isWindCourseWithinRange(350), true);
assertEquals(range.isWindCourseWithinRange(0), true);
assertEquals(range.isWindCourseWithinRange(10), true);
assertEquals(range.isWindCourseWithinRange(349), false);
assertEquals(range.isWindCourseWithinRange(180), false);
assertEquals(range.isWindCourseWithinRange(11), false);
range = new WindRangeForManeuverNode(340, 20);
assertEquals(range.isWindCourseWithinRange(0), true);
assertEquals(range.isWindCourseWithinRange(1), false);
assertEquals(range.isWindCourseWithinRange(339), false);
assertEquals(range.isWindCourseWithinRange(180), false);
range = new WindRangeForManeuverNode(340, 350);
assertEquals(range.isWindCourseWithinRange(341), true);
assertEquals(range.isWindCourseWithinRange(330), true);
assertEquals(range.isWindCourseWithinRange(331), false);
assertEquals(range.isWindCourseWithinRange(339), false);
}
@Test
public void testWindRangeIntersectionCalculation() {
WindRangeForManeuverNode range = new WindRangeForManeuverNode(20, 15);
WindRangeForManeuverNode other = new WindRangeForManeuverNode(10, 20);
IntersectedWindRange intersect = range.intersect(other);
assertEquals(intersect.getFromPortside(), 20, TOLERANCE);
assertEquals(intersect.getAngleTowardStarboard(), 10, TOLERANCE);
assertEquals(intersect.getViolationRange(), 0, TOLERANCE);
IntersectedWindRange intersect2 = other.intersect(range);
assertEquals(intersect, intersect2);
range = new WindRangeForManeuverNode(340, 15);
other = new WindRangeForManeuverNode(350, 20);
intersect = range.intersect(other);
assertEquals(intersect.getFromPortside(), 350, TOLERANCE);
assertEquals(intersect.getAngleTowardStarboard(), 5, TOLERANCE);
assertEquals(intersect.getViolationRange(), 0, TOLERANCE);
intersect2 = other.intersect(range);
assertEquals(intersect, intersect2);
range = new WindRangeForManeuverNode(340, 30);
other = new WindRangeForManeuverNode(10, 20);
intersect = range.intersect(other);
assertEquals(intersect.getFromPortside(), 10, TOLERANCE);
assertEquals(intersect.getAngleTowardStarboard(), 0, TOLERANCE);
assertEquals(intersect.getViolationRange(), 0, TOLERANCE);
intersect2 = other.intersect(range);
assertEquals(intersect, intersect2);
range = new WindRangeForManeuverNode(340, 30);
other = new WindRangeForManeuverNode(330, 20);
intersect = range.intersect(other);
assertEquals(intersect.getFromPortside(), 340, TOLERANCE);
assertEquals(intersect.getAngleTowardStarboard(), 10, TOLERANCE);
assertEquals(intersect.getViolationRange(), 0, TOLERANCE);
intersect2 = other.intersect(range);
assertEquals(intersect, intersect2);
}
@Test
public void testWindRangeIntersectionCalculationWithViolations() {
WindRangeForManeuverNode range = new WindRangeForManeuverNode(20, 15);
WindRangeForManeuverNode other = new WindRangeForManeuverNode(10, 4);
IntersectedWindRange intersect = range.intersect(other);
assertEquals(intersect.getFromPortside(), 10, TOLERANCE);
assertEquals(intersect.getAngleTowardStarboard(), 4, TOLERANCE);
assertEquals(intersect.getViolationRange(), 6, TOLERANCE);
IntersectedWindRange intersect2 = other.intersect(range);
assertEquals(new IntersectedWindRange(range.getFromPortside(), range.getAngleTowardStarboard(),
intersect.getViolationRange()), intersect2);
}
@Test
public void testWindRangeInversion() {
WindRangeForManeuverNode range = new WindRangeForManeuverNode(10, 15);
WindRangeForManeuverNode invert = range.invert();
assertEquals(invert.getFromPortside(), 25, TOLERANCE);
assertEquals(invert.getAngleTowardStarboard(), 345, TOLERANCE);
assertEquals(invert.invert(), range);
}
}
@@ -32,7 +32,8 @@ public class ManeuverTypeGraphBasedWindEstimatorImpl
List<CompetitorTrackWithEstimationData<ManeuverForEstimation>> transformedCompetitorTracks = maneuverForEstimationTransformer
.transform(filteredCompetitorTracks);
ManeuverTypeSequenceGraph maneuverGraph = new ManeuverTypeSequenceGraph(transformedCompetitorTracks,
new ManeuverClassifiersCache(60000, false, true, getPolarService()), new ManeuverNodeBestPathsCalculator());
new ManeuverClassifiersCache(60000, false, true, getPolarService()),
new ManeuverNodeBestPathsCalculator());
return maneuverGraph.estimateWindTrack();
}
@@ -112,6 +112,7 @@ public abstract class AbstractPersistenceManager<T> implements PersistenceManage
private final long numberOfElements;
private long currentElementNumber = 0;
private int numberOfCharsDuringLastStatusLog = 0;
private long limit = Long.MAX_VALUE;
public PersistedElementsIteratorImpl() {
numberOfElements = countElements();
@@ -133,21 +134,27 @@ public abstract class AbstractPersistenceManager<T> implements PersistenceManage
private void prepareNext() {
long nextElementNumber = currentElementNumber + 1;
if (nextElementNumber % (numberOfElements / 100) == 0) {
LoggingUtil.delete(numberOfCharsDuringLastStatusLog);
numberOfCharsDuringLastStatusLog = LoggingUtil
.logInfo("Loading element " + nextElementNumber + "/" + numberOfElements + " ("
+ (nextElementNumber * 100 / numberOfElements) + " %) from " + getCollectionName());
}
try {
Pair<String, T> nextElementWithDbId = getNextElement(lastDbId);
if (this.nextElement != null) {
this.lastDbId = nextElementWithDbId.getA();
this.nextElement = nextElementWithDbId.getB();
this.currentElementNumber = nextElementNumber;
if (nextElementNumber <= limit) {
if (nextElementNumber % (numberOfElements / 100) == 0) {
LoggingUtil.delete(numberOfCharsDuringLastStatusLog);
numberOfCharsDuringLastStatusLog = LoggingUtil
.logInfo("Loading element " + nextElementNumber + "/" + numberOfElements + " ("
+ (nextElementNumber * 100 / numberOfElements) + " %) from " + getCollectionName());
}
} catch (JsonDeserializationException | ParseException e) {
throw new RuntimeException(e);
try {
Pair<String, T> nextElementWithDbId = getNextElement(lastDbId);
if (nextElementWithDbId != null) {
this.lastDbId = nextElementWithDbId.getA();
this.nextElement = nextElementWithDbId.getB();
this.currentElementNumber = nextElementNumber;
} else {
this.nextElement = null;
}
} catch (JsonDeserializationException | ParseException e) {
throw new RuntimeException(e);
}
} else {
this.nextElement = null;
}
}
@@ -155,6 +162,12 @@ public abstract class AbstractPersistenceManager<T> implements PersistenceManage
return numberOfElements;
}
@Override
public PersistedElementsIterator<T> limit(long limit) {
this.limit = limit;
return this;
}
}
}
@@ -32,7 +32,7 @@ public abstract class AbstractRaceWithEstimationDataPersistenceManager<T> extend
@Override
public void dropDb() {
getDb().dropDatabase();
getDb().getCollection(getCollectionName()).drop();
}
public abstract CompetitorTrackWithEstimationDataJsonDeserializer<T> getNewCompetitorTrackWithEstimationDataJsonDeserializer();
@@ -5,5 +5,7 @@ import java.util.Iterator;
public interface PersistedElementsIterator<T> extends Iterator<T> {
long getNumberOfElements();
PersistedElementsIterator<T> limit(long limit);
}
@@ -38,10 +38,4 @@ public class RaceWithCompleteManeuverCurvePersistenceManager
completeManeuverCurveDeserializer);
}
@Override
public void dropDb() {
// TODO Auto-generated method stub
}
}
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.windestimation.data.CompetitorTrackWithEstimationData;
import com.sap.sailing.windestimation.data.ManeuverCategory;
import com.sap.sailing.windestimation.data.ManeuverForEstimation;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
@@ -150,7 +151,8 @@ public class EstimationDataUtil {
List<ManeuverForEstimation> usefulManeuversSortedByTimePoint = new ArrayList<>();
for (CompetitorTrackWithEstimationData<ManeuverForEstimation> competitorTrack : competitorTracks) {
for (ManeuverForEstimation maneuver : competitorTrack.getElements()) {
if (maneuver.isClean() || maneuver.isCleanBefore() || maneuver.isCleanAfter()) {
if (maneuver.isClean() && maneuver.getManeuverCategory() == ManeuverCategory.REGULAR
|| maneuver.getManeuverCategory() == ManeuverCategory.MARK_PASSING) {
usefulManeuversSortedByTimePoint.add(maneuver);
}
}
@@ -160,4 +162,17 @@ public class EstimationDataUtil {
return usefulManeuversSortedByTimePoint;
}
public static List<ManeuverForEstimation> getManeuversSortedByTimePoint(
List<CompetitorTrackWithEstimationData<ManeuverForEstimation>> competitorTracks) {
List<ManeuverForEstimation> maneuversSortedByTimePoint = new ArrayList<>();
for (CompetitorTrackWithEstimationData<ManeuverForEstimation> competitorTrack : competitorTracks) {
for (ManeuverForEstimation maneuver : competitorTrack.getElements()) {
maneuversSortedByTimePoint.add(maneuver);
}
}
Collections.sort(maneuversSortedByTimePoint,
(o1, o2) -> o1.getManeuverTimePoint().compareTo(o2.getManeuverTimePoint()));
return maneuversSortedByTimePoint;
}
}
@@ -13,14 +13,14 @@ public class ManeuverSequenceGraphBasedWindEstimatorEvaluationRunner {
public static void main(String[] args) throws Exception {
WindEstimatorEvaluator<CompleteManeuverCurveWithEstimationData> evaluator = new WindEstimationEvaluatorImpl<>(
15, 2, 0.8);
20, 2, 0.8);
LoggingUtil.logInfo("Connecting to MongoDB");
RaceWithCompleteManeuverCurvePersistenceManager persistenceManager = new RaceWithCompleteManeuverCurvePersistenceManager();
LoggingUtil.logInfo("Loading polar data");
PolarDataService polarService = PolarDataServiceAccessUtil.getPersistedPolarService();
LoggingUtil.logInfo("Wind estimator evaluation started...");
PersistedElementsIterator<RaceWithEstimationData<CompleteManeuverCurveWithEstimationData>> racesIterator = persistenceManager
.getIterator();
.getIterator().limit(20);
WindEstimatorEvaluationResult evaluationResult = evaluator.evaluateWindEstimator(
new ManeuverSequenceGraphBasedWindEstimatorFactory(polarService),
@@ -88,7 +88,7 @@ public class WindEstimationEvaluatorImpl<T> implements WindEstimatorEvaluator<T>
}
LoggingUtil.logInfo("Evaluating on " + raceWithEstimationData.getRegattaName() + " Race "
+ raceWithEstimationData.getRaceName() + " succeeded");
result.printEvaluationStatistics(true);
result.printEvaluationStatistics(false);
return result.getAvgAsSingleResult(minAccuracyPerRaceForCorrectEstimation - 0.00001);
}
@@ -238,11 +238,11 @@ public class WindEstimatorEvaluationResult {
+ (numberOfCorrectWindDirectionEstimations + numberOfIncorrectWindDirectionEstimations) + " correct)");
System.out.println(" Avg. wind course error : "
+ formatDegrees(getAvgAbsWindCourseErrorInDegreesOfCorrectWindDirectionEstimations()));
System.out.println(" Avg. wind course error of incorrect estimations : "
+ formatDegrees(getAvgAbsWindCourseErrorInDegreesOfIncorrectWindDirectionEstimations()));
System.out.println(" Avg. wind course error of all estimations : "
+ formatDegrees(getAvgAbsWindCourseErrorInDegreesOfCorrectAndIncorrectWindDirectionEstimations()));
if (detailed) {
System.out.println(" Avg. wind course error of incorrect estimations : "
+ formatDegrees(getAvgAbsWindCourseErrorInDegreesOfIncorrectWindDirectionEstimations()));
System.out.println(" Avg. wind course error of all estimations : "
+ formatDegrees(getAvgAbsWindCourseErrorInDegreesOfCorrectAndIncorrectWindDirectionEstimations()));
System.out.println();
System.out.println("### Wind speed ###");
System.out.println(" Accuracy: " + formatPercentage(getAccuracyOfWindSpeedEstimation()) + " ("
@@ -3,7 +3,6 @@ package com.sap.sailing.windestimation.maneuverclassifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.json.simple.parser.ParseException;
@@ -36,14 +35,11 @@ public class ManeuverClassifierTrainer {
}
public void trainClassifier(TrainableSingleManeuverOfflineClassifier classifier) throws Exception {
LoggingUtil.logInfo("Connecting to MongoDB");
persistenceManager.createIfNotExistsCollectionWithTransformedManeuvers();
LoggingUtil.logInfo("Querying train dataset...");
Long fixesCountForBoatClass = classifier.getBoatClass() == null ? null
: polarService.getFixCountPerBoatClass().get(classifier.getBoatClass());
List<ManeuverForEstimation> maneuvers = getSuitableManeuvers(classifier.getManeuverFeatures(),
classifier.getBoatClass());
LoggingUtil.logInfo("Queried " + maneuvers.size() + " maneuvers");
LoggingUtil.logInfo("Using " + maneuvers.size() + " maneuvers");
if (maneuvers.size() < MIN_MANEUVERS_COUNT) {
LoggingUtil.logInfo("Not enough maneuver data for training. Training aborted!");
} else {
@@ -74,13 +70,20 @@ public class ManeuverClassifierTrainer {
private List<ManeuverForEstimation> getSuitableManeuvers(ManeuverFeatures maneuverFeatures, BoatClass boatClass)
throws JsonDeserializationException, ParseException {
if (boatClass == null && allManeuvers != null) {
return allManeuvers;
}
Pair<String, ManeuverFeatures> key = new Pair<>(boatClass == null ? null : boatClass.getName(),
maneuverFeatures);
List<ManeuverForEstimation> maneuvers = maneuversPerBoatClass.get(key);
if (maneuvers == null) {
if (allManeuvers == null) {
LoggingUtil.logInfo("Connecting to MongoDB");
persistenceManager.createIfNotExistsCollectionWithTransformedManeuvers();
LoggingUtil.logInfo("Querying dataset...");
allManeuvers = persistenceManager.getAllElements();
}
LoggingUtil.logInfo("Filtering maneuvers for boat class: " + boatClass.getName());
maneuvers = allManeuvers.stream()
.filter(maneuver -> MLUtil.isManeuverContainsAllFeatures(maneuver, maneuverFeatures, boatClass))
.collect(Collectors.toList());
@@ -91,7 +94,8 @@ public class ManeuverClassifierTrainer {
public static void main(String[] args) throws Exception {
PolarDataService polarService = PolarDataServiceAccessUtil.getPersistedPolarService();
Set<BoatClass> allBoatClasses = polarService.getAllBoatClassesWithPolarSheetsAvailable();
ManeuverClassifierTrainer classifierTrainer = new ManeuverClassifierTrainer(
new RegularManeuversForEstimationPersistenceManager(), polarService);
for (ManeuverFeatures maneuverFeatures : ManeuverFeatures.values()) {
LoggingUtil.logInfo(
"### Training classifier for all boat classes with maneuver features: " + maneuverFeatures);
@@ -99,26 +103,23 @@ public class ManeuverClassifierTrainer {
.getAllTrainableClassifierInstances(maneuverFeatures, null);
for (TrainableSingleManeuverOfflineClassifier classifier : allTrainableClassifierInstances) {
LoggingUtil.logInfo("## Classifier: " + classifier.getClass().getName());
ManeuverClassifierTrainer classifierTrainer = new ManeuverClassifierTrainer(
new RegularManeuversForEstimationPersistenceManager(), polarService);
classifierTrainer.trainClassifier(classifier);
}
}
// Set<BoatClass> allBoatClasses = polarService.getAllBoatClassesWithPolarSheetsAvailable();
for (ManeuverFeatures maneuverFeatures : ManeuverFeatures.values()) {
for (BoatClass boatClass : allBoatClasses) {
LoggingUtil.logInfo("### Training classifier for boat class " + boatClass + " with maneuver features: "
+ maneuverFeatures);
List<TrainableSingleManeuverOfflineClassifier> allTrainableClassifierInstances = ManeuverClassifiersFactory
.getAllTrainableClassifierInstances(maneuverFeatures, boatClass);
for (TrainableSingleManeuverOfflineClassifier classifier : allTrainableClassifierInstances) {
LoggingUtil.logInfo("## Classifier: " + classifier.getClass().getName());
ManeuverClassifierTrainer classifierTrainer = new ManeuverClassifierTrainer(
new RegularManeuversForEstimationPersistenceManager(), polarService);
classifierTrainer.trainClassifier(classifier);
}
}
}
// for (ManeuverFeatures maneuverFeatures : ManeuverFeatures.values()) {
// for (BoatClass boatClass : allBoatClasses) {
// LoggingUtil.logInfo("### Training classifier for boat class " + boatClass + " with maneuver features: "
// + maneuverFeatures);
// List<TrainableSingleManeuverOfflineClassifier> allTrainableClassifierInstances = ManeuverClassifiersFactory
// .getAllTrainableClassifierInstances(maneuverFeatures, boatClass);
// for (TrainableSingleManeuverOfflineClassifier classifier : allTrainableClassifierInstances) {
// LoggingUtil.logInfo("## Classifier: " + classifier.getClass().getName());
// classifierTrainer.trainClassifier(classifier);
// }
// }
// }
}
}
@@ -7,11 +7,7 @@ import java.util.stream.Collectors;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.windestimation.maneuverclassifier.impl.ManeuverFeatures;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.GradientBoostingManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.LDAManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.LogisticRegressionManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.NaiveBayesManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.NeuralNetworkManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.QDAManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.RandomForestManeuverClassifier;
import com.sap.sailing.windestimation.maneuverclassifier.impl.smile.SVMManeuverClassifier;
@@ -25,22 +21,23 @@ public class ManeuverClassifiersFactory {
public static TrainableSingleManeuverOfflineClassifier getNewClassifierInstance(ManeuverFeatures maneuverFeatures,
BoatClass boatClass) {
if (boatClass == null) {
return new RandomForestManeuverClassifier(maneuverFeatures, null, supportedManeuverTypes);
} else {
return new SVMManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes);
}
return new NeuralNetworkManeuverClassifier(maneuverFeatures, null, supportedManeuverTypes);
// if (boatClass == null) {
// return new RandomForestManeuverClassifier(maneuverFeatures, null, supportedManeuverTypes);
// } else {
// return new SVMManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes);
// }
}
public static List<TrainableSingleManeuverOfflineClassifier> getAllTrainableClassifierInstances(
ManeuverFeatures maneuverFeatures, BoatClass boatClass) {
List<TrainableSingleManeuverOfflineClassifier> classifiers = new ArrayList<>();
classifiers.add(new GradientBoostingManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new LDAManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new LogisticRegressionManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new NaiveBayesManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
// classifiers.add(new LDAManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
// classifiers.add(new LogisticRegressionManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
// classifiers.add(new NaiveBayesManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new NeuralNetworkManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new QDAManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
// classifiers.add(new QDAManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new RandomForestManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
classifiers.add(new SVMManeuverClassifier(maneuverFeatures, boatClass, supportedManeuverTypes));
List<TrainableSingleManeuverOfflineClassifier> suitableClassifiers = classifiers.stream()
@@ -26,6 +26,10 @@ public class ManeuverEstimationResult {
public ManeuverEstimationResult(ManeuverForEstimation maneuver, double[] likelihoodPerManeuverType) {
this.maneuver = maneuver;
for (int i = 0; i < likelihoodPerManeuverType.length; i++) {
likelihoodPerManeuverType[i] += 0.1;
}
ProbabilityUtil.normalizeLikelihoodArray(likelihoodPerManeuverType);
this.likelihoodPerManeuverType = likelihoodPerManeuverType;
computeLikelihoodsForPointOfSailAfterManeuver();
}
@@ -0,0 +1,67 @@
package com.sap.sailing.windestimation.maneuverclassifier;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.windestimation.data.persistence.PolarDataServiceAccessUtil;
import com.sap.sailing.windestimation.maneuverclassifier.impl.ManeuverFeatures;
import com.sap.sailing.windestimation.util.LoggingUtil;
public class PersistedManeuverClassifiersScorePrinter {
private PersistedManeuverClassifiersScorePrinter() {
}
public static void main(String[] args)
throws MalformedURLException, ClassNotFoundException, IOException, InterruptedException {
PolarDataService polarService = PolarDataServiceAccessUtil.getPersistedPolarService();
Set<BoatClass> allBoatClasses = polarService.getAllBoatClassesWithPolarSheetsAvailable();
List<TrainableSingleManeuverOfflineClassifier> allBoatClassesClassifiers = new ArrayList<>();
LoggingUtil.logInfo("### Loading all boat class classifiers:");
for (ManeuverFeatures maneuverFeatures : ManeuverFeatures.values()) {
List<TrainableSingleManeuverOfflineClassifier> classifiers = ManeuverClassifiersFactory
.getAllTrainableClassifierInstances(maneuverFeatures, null);
for (TrainableSingleManeuverOfflineClassifier classifier : classifiers) {
try {
classifier.loadPersistedModel();
allBoatClassesClassifiers.add(classifier);
} catch (ClassifierPersistenceException e) {
e.printStackTrace();
}
}
for (BoatClass boatClass : allBoatClasses) {
classifiers = ManeuverClassifiersFactory.getAllTrainableClassifierInstances(maneuverFeatures,
boatClass);
for (TrainableSingleManeuverOfflineClassifier classifier : classifiers) {
try {
classifier.loadPersistedModel();
allBoatClassesClassifiers.add(classifier);
} catch (ClassifierPersistenceException e) {
}
}
}
}
StringBuilder str = new StringBuilder("Classifier name \t| Maneuver features \t| Boat class \t| Test score");
Collections.sort(allBoatClassesClassifiers, (a, b) -> Double.compare(a.getTestScore(), b.getTestScore()));
for (TrainableSingleManeuverOfflineClassifier classifier : allBoatClassesClassifiers) {
str.append("\r\n" + classifier.getClass().getSimpleName() + ": \t| " + classifier.getManeuverFeatures()
+ " \t| " + classifier.getBoatClass() + " \t| "
+ String.format(" %.03f", classifier.getTestScore()));
}
String outputStr = str.toString();
LoggingUtil.logInfo(outputStr);
outputStr = outputStr.replaceAll(Pattern.quote(" \t| "), ";");
Files.write(Paths.get("classifierScores.csv"), outputStr.getBytes());
}
}
@@ -49,6 +49,10 @@ public class MLUtil {
return false;
}
}
if (boatClass == null
|| maneuver.getBoatClass() != null && !boatClass.getName().equals(maneuver.getBoatClass().getName())) {
return false;
}
return true;
}
@@ -6,6 +6,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.List;
import com.sap.sailing.domain.base.BoatClass;
@@ -56,7 +57,8 @@ public abstract class AbstractSmileManeuverClassifier<T extends SoftClassifier<d
this.pca = copy.pca;
this.scaler = copy.scaler;
this.fixesCountForBoatClass = copy.fixesCountForBoatClass;
if (!getBoatClass().equals(copy.getBoatClass())) {
if (getBoatClass() != copy.getBoatClass() && getBoatClass() != null && (copy.getBoatClass() == null
|| !getBoatClass().getName().equals(copy.getBoatClass().getName()))) {
throw new ClassifierPersistenceException("The boat class of the loaded classifier is: "
+ copy.getBoatClass() + ". \nExpected: " + getBoatClass());
}
@@ -64,7 +66,7 @@ public abstract class AbstractSmileManeuverClassifier<T extends SoftClassifier<d
throw new ClassifierPersistenceException("The maneuver features of the loaded classifier are: "
+ copy.getManeuverFeatures() + ". \nExpected: " + getManeuverFeatures());
}
if (!getSupportedManeuverTypesMapping().equals(copy.getSupportedManeuverTypesMapping())) {
if (!Arrays.equals(getSupportedManeuverTypesMapping(), copy.getSupportedManeuverTypesMapping())) {
throw new ClassifierPersistenceException("The supported maneuver types of the loaded classifier are: "
+ copy.getSupportedManeuverTypes() + ". \nExpected: " + getSupportedManeuverTypes());
}
@@ -6,6 +6,7 @@ import com.sap.sailing.windestimation.maneuverclassifier.PreprocessingConfig.Pre
import com.sap.sailing.windestimation.maneuverclassifier.impl.ManeuverFeatures;
import smile.classification.SVM;
import smile.classification.SVM.Multiclass;
import smile.math.kernel.GaussianKernel;
public class SVMManeuverClassifier extends AbstractSmileManeuverClassifier<SVM<double[]>> {
@@ -22,9 +23,11 @@ public class SVMManeuverClassifier extends AbstractSmileManeuverClassifier<SVM<d
int featuresCount = x[0].length;
double gamma = 1.0 / featuresCount;
double sigma = Math.sqrt(0.5 / gamma);
SVM<double[]> svm = new SVM<>(new GaussianKernel(sigma), 10.0);
SVM<double[]> svm = new SVM<>(new GaussianKernel(sigma), 10.0, getSupportedManeuverTypesCount(),
Multiclass.ONE_VS_ALL);
svm.learn(x, y);
svm.finish();
svm.trainPlattScaling(x, y);
return svm;
}
@@ -17,4 +17,27 @@ public class IntersectedWindRange extends WindRangeForManeuverNode {
return violationRange > 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(violationRange);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
IntersectedWindRange other = (IntersectedWindRange) obj;
if (Double.doubleToLongBits(violationRange) != Double.doubleToLongBits(other.violationRange))
return false;
return true;
}
}
@@ -6,18 +6,16 @@ import com.sap.sailing.windestimation.maneuverclassifier.ManeuverTypeForClassifi
public class ManeuverNode {
private final WindRangeForManeuverNode validWindRange;
private final boolean windRangeToExclude;
private double confidence;
private final int indexInLevel;
private final ManeuverTypeForClassification maneuverType;
private final Tack tackAfter;
public ManeuverNode(ManeuverTypeForClassification maneuverType, Tack tackAfter,
WindRangeForManeuverNode validWindRange, boolean windRangeToExclude, double confidence, int indexInLevel) {
WindRangeForManeuverNode validWindRange, double confidence, int indexInLevel) {
this.maneuverType = maneuverType;
this.tackAfter = tackAfter;
this.validWindRange = validWindRange;
this.windRangeToExclude = windRangeToExclude;
this.confidence = confidence;
this.indexInLevel = indexInLevel;
}
@@ -30,10 +28,6 @@ public class ManeuverNode {
return validWindRange;
}
public boolean isWindRangeToExclude() {
return windRangeToExclude;
}
public double getConfidence() {
return confidence;
}
@@ -10,7 +10,6 @@ import java.util.Map;
import java.util.Map.Entry;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
@@ -18,9 +17,9 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
import com.sap.sailing.windestimation.data.FineGrainedManeuverType;
import com.sap.sailing.windestimation.data.FineGrainedPointOfSail;
import com.sap.sailing.windestimation.data.ManeuverForEstimation;
import com.sap.sailing.windestimation.maneuverclassifier.ManeuverTypeForClassification;
import com.sap.sailing.windestimation.polarsfitting.SailingStatistics;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Speed;
@@ -35,7 +34,7 @@ import com.sap.sse.common.impl.DegreeBearingImpl;
public class ManeuverNodeBestPathsCalculator {
protected static final double INTERVAL_FOR_WIND_PATH_DEVIATION_ANALYSIS_IN_SECONDS = 30 * 60;
protected static final double MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES = 45;
protected static final double MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES = 30;
private ManeuverNodeGraphLevel lastLevel;
@@ -123,13 +122,14 @@ public class ManeuverNodeBestPathsCalculator {
.getPathSailingStatistics(previousLevel.getManeuver().getBoatClass());
IntersectedWindRange intersectedWindRangeUntilCurrentNode = bestPreviousNodeInfo.getWindRange()
.intersect(currentNode.getValidWindRange());
double probability = bestPathsUntilPreviousLevel
.getNormalizedProbabilityToNodeFromStart(previousNode)
double probability = bestPathsUntilPreviousLevel.getNormalizedProbabilityToNodeFromStart(
previousNode) * getPenaltyFactorForTackTransition(currentLevel, previousNode, currentNode)
* currentNode.getConfidence()
* getPenaltyFactorForTransitionConsideringWindRangeWithinBestPath(
intersectedWindRangeUntilCurrentNode, secondsPassedSincePreviousManeuver)
* getPenaltyFactorForTransitionConsideringPreviousPathStats(currentLevel, currentNode,
previousPathStats, intersectedWindRangeUntilCurrentNode);
assert (probability > 0.0001);
if (probability > maxProbability) {
maxProbability = probability;
bestPreviousNode = previousNode;
@@ -145,6 +145,33 @@ public class ManeuverNodeBestPathsCalculator {
this.lastLevel = currentLevel;
}
protected double getPenaltyFactorForTackTransition(ManeuverNodeGraphLevel currentLevel, ManeuverNode previousNode,
ManeuverNode currentNode) {
// if (previousNode.getManeuverType() != ManeuverTypeForClassification.OTHER
// && currentNode.getManeuverType() != ManeuverTypeForClassification.OTHER) {
// if (previousNode.getTackAfter() == currentNode.getTackAfter()) {
// return 0.05;
// }
// } else if (previousNode.getManeuverType() == currentNode.getManeuverType()) {
// // maneuver type is OTHER
// if (previousNode.getTackAfter() != currentNode.getTackAfter()) {
// return 0.05;
// }
// } else {
// // one maneuver is OTHER, other maneuver is tack or jibe
// if (previousNode.getManeuverType() == ManeuverTypeForClassification.OTHER) {
// if (previousNode.getTackAfter() == currentNode.getTackAfter()) {
// return 0.05;
// }
// } else {
// if (previousNode.getTackAfter() != currentNode.getTackAfter()) {
// return 0.05;
// }
// }
// }
return 1;
}
protected double getPenaltyFactorForTransitionConsideringWindRangeWithinBestPath(
IntersectedWindRange intersectedWindRangeUntilCurrentNode, double secondsPassedSincePreviousWindRange) {
double violationRange = intersectedWindRangeUntilCurrentNode.getViolationRange();
@@ -152,18 +179,19 @@ public class ManeuverNodeBestPathsCalculator {
if (violationRange == 0) {
penaltyFactor = 1.0;
} else {
violationRange -= secondsPassedSincePreviousWindRange / 3600
* MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES;
if (violationRange <= 0) {
penaltyFactor = 1 / (1
+ (MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES + violationRange)
* 0.01);
} else if (violationRange <= MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES) {
penaltyFactor = 1 / (1 + Math.pow((violationRange) / 15, 2));
} else {
penaltyFactor = 1 / (4 + Math.pow((violationRange) / 5, 2));
}
// violationRange -= Math.max(1, secondsPassedSincePreviousWindRange / 3600)
// * MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES;
// if (violationRange <= 0) {
// penaltyFactor = 1 / (1
// + (MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES + violationRange)
// * 0.1);
// if (violationRange <= MAX_ABS_WIND_COURSE_DEVIATION_TOLERANCE_WITHIN_ANALYSIS_INTERVAL_IN_DEGREES) {
// penaltyFactor = 1 / (1 + Math.pow((violationRange) / 15, 2));
// } else {
penaltyFactor = 1 / (1 + (Math.pow(violationRange, 2)));
// }
}
assert (penaltyFactor > 0.0001);
return penaltyFactor;
}
@@ -188,7 +216,9 @@ public class ManeuverNodeBestPathsCalculator {
lowestSpeedAndTurningRatePenaltyFactor = getLowestSpeedAndTurningRatePenaltyFactor(previousPathStats,
currentLevel, currentNode);
}
return speedPenaltyFactorBefore * speedPenaltyFactorAfter * lowestSpeedAndTurningRatePenaltyFactor;
double finalPenaltyFactor = speedPenaltyFactorBefore * speedPenaltyFactorAfter
* lowestSpeedAndTurningRatePenaltyFactor;
return finalPenaltyFactor;
}
}
@@ -212,78 +242,12 @@ public class ManeuverNodeBestPathsCalculator {
protected double getLowestSpeedAndTurningRatePenaltyFactor(SailingStatistics averageStatistics,
ManeuverNodeGraphLevel currentLevel, ManeuverNode currentNode) {
double tackProbabilityBonus = 0;
ManeuverForEstimation maneuver = currentLevel.getManeuver();
double absDirectionChangeInDegrees = Math.abs(maneuver.getCourseChangeWithinMainCurveInDegrees());
double lowestSpeedRatio = (maneuver.getSpeedLossRatio() + maneuver.getLowestSpeedVsExitingSpeedRatio()) / 2.0;
double turningRate = maneuver.getMaxTurningRateInDegreesPerSecond();
switch (currentNode.getManeuverType()) {
case TACK:
for (FineGrainedManeuverType otherManeuverType : FineGrainedManeuverType.values()) {
if (otherManeuverType != FineGrainedManeuverType.TACK
&& otherManeuverType != FineGrainedManeuverType._180_JIBE
&& otherManeuverType != FineGrainedManeuverType._180_TACK
&& otherManeuverType != FineGrainedManeuverType._360
&& averageStatistics.getNumberOfCleanManeuvers(otherManeuverType) > 0) {
double lowestSpeedRatioDifference = lowestSpeedRatio
- averageStatistics.getAverageSpeedLossForManeuverType(otherManeuverType);
double turningRateDifference = turningRate
- averageStatistics.getAverageTurningRateForManeuverType(otherManeuverType);
if (lowestSpeedRatioDifference > 0 && turningRateDifference < 0) {
tackProbabilityBonus -= lowestSpeedRatioDifference * 2 + turningRateDifference / -100;
}
}
}
double courseChangeDifference = absDirectionChangeInDegrees
- averageStatistics.getAverageAbsCourseChangeInDegreesForManeuverType(FineGrainedManeuverType.JIBE);
if (courseChangeDifference < 0) {
tackProbabilityBonus -= courseChangeDifference / -180;
}
break;
case JIBE:
case OTHER:
if (averageStatistics.getNumberOfCleanManeuvers(FineGrainedManeuverType.TACK) > 0) {
double lowestSpeedRatioDifference = lowestSpeedRatio
- averageStatistics.getAverageSpeedLossForManeuverType(FineGrainedManeuverType.TACK);
double turningRateDifference = turningRate
- averageStatistics.getAverageTurningRateForManeuverType(FineGrainedManeuverType.TACK);
if (lowestSpeedRatioDifference < 0 && turningRateDifference > 0) {
tackProbabilityBonus += lowestSpeedRatioDifference * -2 + turningRateDifference / 100;
}
// courseChangeDifference = absDirectionChangeInDegrees - averageStatistics
// .getAverageAbsCourseChangeInDegreesForManeuverType(FineGrainedManeuverType.TACK);
// if (courseChangeDifference > 0) {
// tackProbabilityBonus += courseChangeDifference / 180;
// }
}
break;
}
return 1 - Math.abs(tackProbabilityBonus);
return 1;
}
protected double getSpeedPenaltyFactorForPointOfSail(SailingStatistics averageStatistics,
FineGrainedPointOfSail pointOfSail, double speedAtPointOfSail, BoatClass boatClass) {
double lowestAverageSpeedUpwind = averageStatistics.getLowestUpwindAvgSpeed();
double upwindProbabilityBonus = 0.0;
if (pointOfSail.getLegType() == LegType.UPWIND) {
for (FineGrainedPointOfSail otherPointOfSail : FineGrainedPointOfSail.values()) {
if (otherPointOfSail.getLegType() == LegType.REACHING
|| otherPointOfSail.getLegType() == LegType.DOWNWIND
&& averageStatistics.getNumberOfCleanTracks(otherPointOfSail) > 0) {
double speedRatio = speedAtPointOfSail
/ averageStatistics.getAverageSpeedInKnotsForPointOfSail(otherPointOfSail);
if (speedRatio > 1.05) {
upwindProbabilityBonus = Math.min(upwindProbabilityBonus, Math.max((1 - speedRatio), -0.8));
}
}
}
} else if (lowestAverageSpeedUpwind != 0) {
double speedRatio = lowestAverageSpeedUpwind / speedAtPointOfSail;
if (speedRatio > 1.05) {
upwindProbabilityBonus = Math.max((1 - speedRatio), -0.8);
}
}
return 1.0 - Math.abs(upwindProbabilityBonus);
return 1;
}
public List<Pair<ManeuverNodeGraphLevel, ManeuverNode>> getBestPath(ManeuverNodeGraphLevel lastLevel,
@@ -345,7 +309,8 @@ public class ManeuverNodeBestPathsCalculator {
.getBestPreviousNodeInfo(currentNode);
globalWindRange = globalWindRange == null ? bestPreviousNodeInfo.getWindRange()
: globalWindRange.intersect(bestPreviousNodeInfo.getWindRange());
if (!globalWindRange.isViolation() && globalWindRange.getAngleTowardStarboard() <= 40) {
if (!globalWindRange.isViolation() && globalWindRange.getAngleTowardStarboard() <= 20
&& currentNode.getManeuverType() == ManeuverTypeForClassification.OTHER) {
DegreeBearingImpl windCourse = new DegreeBearingImpl(globalWindRange.getAvgWindCourse());
Speed avgWindSpeed = getAvgWindSpeed(currentLevel.getManeuver(), windCourse);
Wind wind = new WindImpl(currentLevel.getManeuver().getManeuverPosition(),
@@ -0,0 +1,98 @@
package com.sap.sailing.windestimation.maneuvergraph.maneuvernode;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.windestimation.data.FineGrainedManeuverType;
import com.sap.sailing.windestimation.data.FineGrainedPointOfSail;
import com.sap.sailing.windestimation.data.ManeuverForEstimation;
import com.sap.sailing.windestimation.polarsfitting.SailingStatistics;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class ManeuverNodeBestPathsCalculatorWithGeneralPolars extends ManeuverNodeBestPathsCalculator {
@Override
protected double getLowestSpeedAndTurningRatePenaltyFactor(SailingStatistics averageStatistics,
ManeuverNodeGraphLevel currentLevel, ManeuverNode currentNode) {
double tackProbabilityBonus = 0;
ManeuverForEstimation maneuver = currentLevel.getManeuver();
double absDirectionChangeInDegrees = Math.abs(maneuver.getCourseChangeWithinMainCurveInDegrees());
double lowestSpeedRatio = (maneuver.getSpeedLossRatio() + maneuver.getLowestSpeedVsExitingSpeedRatio()) / 2.0;
double turningRate = maneuver.getMaxTurningRateInDegreesPerSecond();
switch (currentNode.getManeuverType()) {
case TACK:
for (FineGrainedManeuverType otherManeuverType : FineGrainedManeuverType.values()) {
if (otherManeuverType != FineGrainedManeuverType.TACK
&& otherManeuverType != FineGrainedManeuverType._180_JIBE
&& otherManeuverType != FineGrainedManeuverType._180_TACK
&& otherManeuverType != FineGrainedManeuverType._360
&& averageStatistics.getNumberOfCleanManeuvers(otherManeuverType) > 0) {
double lowestSpeedRatioDifference = lowestSpeedRatio
- averageStatistics.getAverageSpeedLossForManeuverType(otherManeuverType);
double turningRateDifference = turningRate
- averageStatistics.getAverageTurningRateForManeuverType(otherManeuverType);
if (lowestSpeedRatioDifference > 0 && turningRateDifference < 0) {
tackProbabilityBonus = Math.min(tackProbabilityBonus,
-1 / (1 + lowestSpeedRatioDifference / 2 + turningRateDifference / -100));
}
}
}
double courseChangeDifference = absDirectionChangeInDegrees
- averageStatistics.getAverageAbsCourseChangeInDegreesForManeuverType(FineGrainedManeuverType.JIBE);
if (courseChangeDifference < 0) {
tackProbabilityBonus = (1 + tackProbabilityBonus) * (1 - Math.min(0.1, courseChangeDifference / -360));
}
break;
case JIBE:
case OTHER:
if (averageStatistics.getNumberOfCleanManeuvers(FineGrainedManeuverType.TACK) > 0) {
double lowestSpeedRatioDifference = lowestSpeedRatio
- averageStatistics.getAverageSpeedLossForManeuverType(FineGrainedManeuverType.TACK);
double turningRateDifference = turningRate
- averageStatistics.getAverageTurningRateForManeuverType(FineGrainedManeuverType.TACK);
if (lowestSpeedRatioDifference < 0 && turningRateDifference > 0) {
tackProbabilityBonus += Math.max(tackProbabilityBonus,
1 / (1 + lowestSpeedRatioDifference / -2 + turningRateDifference / 100));
}
// courseChangeDifference = absDirectionChangeInDegrees - averageStatistics
// .getAverageAbsCourseChangeInDegreesForManeuverType(FineGrainedManeuverType.TACK);
// if (courseChangeDifference > 0) {
// tackProbabilityBonus += courseChangeDifference / 180;
// }
}
break;
}
double penaltyFactor = 1 - Math.abs(tackProbabilityBonus);
return penaltyFactor;
}
@Override
protected double getSpeedPenaltyFactorForPointOfSail(SailingStatistics averageStatistics,
FineGrainedPointOfSail pointOfSail, double speedAtPointOfSail, BoatClass boatClass) {
double lowestAverageSpeedUpwind = averageStatistics.getLowestUpwindAvgSpeed();
double upwindProbabilityBonus = 0.0;
if (pointOfSail.getLegType() == LegType.UPWIND) {
for (FineGrainedPointOfSail otherPointOfSail : FineGrainedPointOfSail.values()) {
if (otherPointOfSail.getLegType() == LegType.REACHING
|| otherPointOfSail.getLegType() == LegType.DOWNWIND
&& averageStatistics.getNumberOfCleanTracks(otherPointOfSail) > 0) {
double speedRatio = speedAtPointOfSail
/ averageStatistics.getAverageSpeedInKnotsForPointOfSail(otherPointOfSail);
if (speedRatio > 1.05) {
upwindProbabilityBonus = Math.min(upwindProbabilityBonus, Math.max((1 - speedRatio), -0.8));
}
}
}
} else if (lowestAverageSpeedUpwind != 0) {
double speedRatio = lowestAverageSpeedUpwind / speedAtPointOfSail;
if (speedRatio > 1.05) {
upwindProbabilityBonus = Math.max((1 - speedRatio), -0.8);
}
}
return 1.0 - Math.abs(upwindProbabilityBonus);
}
}
@@ -18,6 +18,7 @@ import com.sap.sse.common.impl.DegreeBearingImpl;
*/
public class ManeuverNodeGraphLevel {
private static final int MIN_UPWIND_ABS_TWA = 20;
private final ManeuverForEstimation maneuver;
private ManeuverEstimationResult maneuverEstimationResult;
@@ -51,15 +52,15 @@ public class ManeuverNodeGraphLevel {
}
private void addManeuverNode(ManeuverTypeForClassification maneuverType, Tack tackAfter,
WindRangeForManeuverNode windRange, boolean windRangeToExclude, double confidence) {
ManeuverNode maneuverNode = new ManeuverNode(maneuverType, tackAfter, windRange, windRangeToExclude,
confidence / probabilitiesSum, levelNodes.size() - 1);
WindRangeForManeuverNode windRange, double confidence) {
ManeuverNode maneuverNode = new ManeuverNode(maneuverType, tackAfter, windRange, confidence / probabilitiesSum,
levelNodes.size());
levelNodes.add(maneuverNode);
}
private void initNodeJustForSpeedAnalysis() {
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(0, 360);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange, false, 1.0);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange, 1.0);
}
private void initNodesAsRegular() {
@@ -86,15 +87,14 @@ public class ManeuverNodeGraphLevel {
private void initBearAwayNode() {
Bearing invertedCourseBefore = maneuver.getSpeedWithBearingBefore().getBearing().reverse();
double angleTowardStarboard = Math.abs(
invertedCourseBefore.getDifferenceTo(maneuver.getSpeedWithBearingAfter().getBearing()).getDegrees());
// subtract minimal upwind TWA which is 30 deg plus -10 deg for buffer
double minUpwindTwa = 20;
angleTowardStarboard -= 20;
double angleTowardStarboard = invertedCourseBefore
.getDifferenceTo(maneuver.getSpeedWithBearingAfter().getBearing()).abs().getDegrees();
angleTowardStarboard -= MIN_UPWIND_ABS_TWA;
assert (angleTowardStarboard > 0);
Bearing from;
Tack tackAfter;
if (maneuver.getCourseChangeInDegrees() < 0) {
from = invertedCourseBefore.add(new DegreeBearingImpl(-minUpwindTwa));
from = invertedCourseBefore.add(new DegreeBearingImpl(MIN_UPWIND_ABS_TWA));
tackAfter = Tack.PORT;
} else {
from = maneuver.getSpeedWithBearingAfter().getBearing();
@@ -102,58 +102,50 @@ public class ManeuverNodeGraphLevel {
}
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(from.getDegrees(), angleTowardStarboard);
double confidence = maneuverEstimationResult.getManeuverTypeLikelihood(CoarseGrainedManeuverType.BEAR_AWAY);
addManeuverNode(ManeuverTypeForClassification.OTHER, tackAfter, windRange, false, confidence);
addManeuverNode(ManeuverTypeForClassification.OTHER, tackAfter, windRange, confidence);
}
private void initHeadUpNode() {
Bearing invertedCourseAfter = maneuver.getSpeedWithBearingAfter().getBearing().reverse();
double angleTowardStarboard = Math.abs(
invertedCourseAfter.getDifferenceTo(maneuver.getSpeedWithBearingBefore().getBearing()).getDegrees());
double angleTowardStarboard = invertedCourseAfter
.getDifferenceTo(maneuver.getSpeedWithBearingBefore().getBearing()).abs().getDegrees();
angleTowardStarboard -= MIN_UPWIND_ABS_TWA;
assert (angleTowardStarboard > 0);
Bearing from;
Tack tackAfter;
if (maneuver.getCourseChangeInDegrees() < 0) {
from = maneuver.getSpeedWithBearingBefore().getBearing();
tackAfter = Tack.STARBOARD;
} else {
from = invertedCourseAfter;
from = invertedCourseAfter.add(new DegreeBearingImpl(MIN_UPWIND_ABS_TWA));
tackAfter = Tack.PORT;
}
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(from.getDegrees(), angleTowardStarboard);
double confidence = maneuverEstimationResult.getManeuverTypeLikelihood(CoarseGrainedManeuverType.HEAD_UP);
addManeuverNode(ManeuverTypeForClassification.OTHER, tackAfter, windRange, false, confidence);
addManeuverNode(ManeuverTypeForClassification.OTHER, tackAfter, windRange, confidence);
}
private void initJibeNode() {
Bearing middleCourse = maneuver.getMiddleCourse();
double absCourseChangeDeg = Math.abs(maneuver.getCourseChangeInDegrees());
Double optimalJibeDeviationDeg = maneuver.getDeviationFromOptimalJibeAngleInDegrees();
double middleAngleRange = 0;
if (optimalJibeDeviationDeg != null) {
middleAngleRange = Math.max(0, optimalJibeDeviationDeg * 2);
}
middleAngleRange += absCourseChangeDeg * 0.4;
double middleAngleRange = absCourseChangeDeg * 0.5;
Bearing from = middleCourse.add(new DegreeBearingImpl(-middleAngleRange / 2.0));
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(from.getDegrees(), middleAngleRange);
double confidence = maneuverEstimationResult.getManeuverTypeLikelihood(CoarseGrainedManeuverType.JIBE);
Tack tackAfter = maneuver.getCourseChangeWithinMainCurveInDegrees() < 0 ? Tack.STARBOARD : Tack.PORT;
addManeuverNode(ManeuverTypeForClassification.JIBE, tackAfter, windRange, false, confidence);
addManeuverNode(ManeuverTypeForClassification.JIBE, tackAfter, windRange, confidence);
}
private void initTackNode(double probabilitiesSum) {
Bearing middleCourse = maneuver.getMiddleCourse();
double absCourseChangeDeg = Math.abs(maneuver.getCourseChangeInDegrees());
Double optimalTackDeviationDeg = maneuver.getDeviationFromOptimalTackAngleInDegrees();
double middleAngleRange = 0;
if (optimalTackDeviationDeg != null) {
middleAngleRange = Math.max(0, optimalTackDeviationDeg * 2);
}
middleAngleRange += absCourseChangeDeg * 0.2;
double middleAngleRange = absCourseChangeDeg * 0.4;
Bearing from = middleCourse.add(new DegreeBearingImpl(-middleAngleRange / 2.0));
from = from.reverse();
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(from.getDegrees(), middleAngleRange);
double confidence = maneuverEstimationResult.getManeuverTypeLikelihood(CoarseGrainedManeuverType.TACK);
Tack tackAfter = maneuver.getCourseChangeWithinMainCurveInDegrees() < 0 ? Tack.PORT : Tack.STARBOARD;
addManeuverNode(ManeuverTypeForClassification.TACK, tackAfter, windRange, false, confidence);
addManeuverNode(ManeuverTypeForClassification.TACK, tackAfter, windRange, confidence);
}
private void initNodesAsPenaltyCircle() {
@@ -167,8 +159,8 @@ public class ManeuverNodeGraphLevel {
from = from.reverse();
double angleTowardStarboard = 140;
WindRangeForManeuverNode windRange = new WindRangeForManeuverNode(from.getDegrees(), angleTowardStarboard);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange, false, 0.7);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange, true, 0.3);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange, 0.7);
addManeuverNode(ManeuverTypeForClassification.OTHER, null, windRange.invert(), 0.3);
}
public ManeuverForEstimation getManeuver() {
@@ -35,12 +35,19 @@ public class WindRangeForManeuverNode {
return angleTowardStarboard;
}
public WindRangeForManeuverNode invert() {
double newFromPortside = fromPortside + angleTowardStarboard;
if (newFromPortside >= 360) {
newFromPortside -= 360;
}
double newAngleTowardStarboard = 360 - angleTowardStarboard;
return new WindRangeForManeuverNode(newFromPortside, newAngleTowardStarboard);
}
public double getAvgWindCourse() {
double avgWindCourse = fromPortside + angleTowardStarboard / 2.0;
if (avgWindCourse > 360) {
avgWindCourse -= 360;
} else if (avgWindCourse < 0) {
avgWindCourse += 360;
}
return avgWindCourse;
}
@@ -88,11 +95,11 @@ public class WindRangeForManeuverNode {
// newFromPortside = nextWindRange.angleTowardStarboard -
// deviationFromPortsideTowardStarboardInDegrees;
// newAngleTowardStarboard = deviationFromPortsideTowardStarboardInDegrees;
violationRange = newAngleTowardStarboard;
violationRange = deviationFromPortsideTowardStarboardInDegrees;
} else {
// newFromPortside = angleTowardStarboard - deviationFromPortsideTowardPortsideInDegrees;
// newAngleTowardStarboard = deviationFromPortsideTowardPortsideInDegrees;
violationRange = newAngleTowardStarboard;
violationRange = deviationFromPortsideTowardPortsideInDegrees;
}
}
}
@@ -230,4 +237,32 @@ public class WindRangeForManeuverNode {
throw new IllegalStateException();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(angleTowardStarboard);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(fromPortside);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WindRangeForManeuverNode other = (WindRangeForManeuverNode) obj;
if (Double.doubleToLongBits(angleTowardStarboard) != Double.doubleToLongBits(other.angleTowardStarboard))
return false;
if (Double.doubleToLongBits(fromPortside) != Double.doubleToLongBits(other.fromPortside))
return false;
return true;
}
}
@@ -0,0 +1,12 @@
package com.sap.sailing.windestimation.polarsfitting;
import com.sap.sailing.domain.polars.PolarDataService;
public class ManeuverPolarsFittingWindEstimation extends PolarsFittingWindEstimation {
public ManeuverPolarsFittingWindEstimation(PolarDataService polarService) {
super(polarService);
// TODO Auto-generated constructor stub
}
}