configuration/environments_scripts/central_mongo_setup/files/etc/mongodb/mongodb-archive.conf
... ...
@@ -14,4 +14,6 @@ systemLog:
14 14
path: "/var/log/mongodb/archive.log"
15 15
replication:
16 16
replSetName: "archive"
17
-
17
+# Disable FTDC to avoid crashes
18
+setParameter:
19
+ diagnosticDataCollectionEnabled: false
configuration/environments_scripts/central_mongo_setup/files/etc/mongodb/mongodb-hidden-live-replica.conf
... ...
@@ -14,4 +14,6 @@ systemLog:
14 14
path: "/var/log/mongodb/hidden-live-replica.log"
15 15
replication:
16 16
replSetName: "live"
17
-
17
+# Disable FTDC to avoid crashes
18
+setParameter:
19
+ diagnosticDataCollectionEnabled: false
configuration/environments_scripts/central_mongo_setup/files/etc/mongodb/mongodb-slow.conf
... ...
@@ -14,4 +14,6 @@ systemLog:
14 14
path: "/var/log/mongodb/slow.log"
15 15
replication:
16 16
replSetName: "slow"
17
-
17
+# Disable FTDC to avoid crashes
18
+setParameter:
19
+ diagnosticDataCollectionEnabled: false
configuration/environments_scripts/central_reverse_proxy/files/home/httpdConf/repo.git/hooks/post-receive
... ...
@@ -12,7 +12,7 @@ COMMAND="sudo service httpd reload"
12 12
MAIN_BRANCH_NAME="main"
13 13
DISPOSABLE_BRANCH_NAME="disposable"
14 14
CENTRAL_BRANCH_NAME="central"
15
-CHECKED_OUT_WORKSPACE_NAME="checked-out" # Assumes it is in the root user.
15
+CHECKED_OUT_WORKSPACE_NAME="checked-out"
16 16
sync_repo() {
17 17
#$1 tag key to use
18 18
#$2 branch to check out
configuration/environments_scripts/mongo_instance_setup/files/etc/mongod.conf
... ...
@@ -43,3 +43,6 @@ replication:
43 43
#auditLog:
44 44
45 45
#snmp:
46
+# Disable FTDC to avoid crashes
47
+setParameter:
48
+ diagnosticDataCollectionEnabled: false
configuration/environments_scripts/repo/usr/local/bin/imageupgrade_functions.sh
... ...
@@ -456,6 +456,12 @@ EOF
456 456
if ! grep "logRotate: reopen" /etc/mongod.conf; then
457 457
sudo sed -i -e 's/^ logAppend: true/ logAppend: true\n logRotate: reopen/' /etc/mongod.conf
458 458
fi
459
+sudo su - -c "cat >>/etc/mongod.conf << EOF
460
+# Disable FTDC to avoid crashes
461
+setParameter:
462
+ diagnosticDataCollectionEnabled: false
463
+EOF
464
+"
459 465
}
460 466
461 467
# Copies the /root/secrets and /root/mail.properties file to the local instance, ensuring only root can read it
configuration/environments_scripts/sailing_server/setup-sailing-server.sh
... ...
@@ -32,7 +32,7 @@ else
32 32
sudo mv imageupgrade_functions.sh /usr/local/bin
33 33
# build-crontab
34 34
. imageupgrade_functions.sh
35
- # Install MongoDB 5.0 and configure as replica set "replica"
35
+ # Install MongoDB 7.0 and configure as replica set "replica"
36 36
setup_mongo_7_0_on_AL2023
37 37
sudo su - -c "cat << EOF >>/etc/mongod.conf
38 38
replication:
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidence.java
... ...
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.common.confidence;
2 2
3 3
import com.sap.sailing.domain.common.DoublePair;
4 4
import com.sap.sse.common.Bearing;
5
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
5 6
6 7
7 8
public interface BearingWithConfidence<RelativeTo> extends HasConfidenceAndIsScalable<DoublePair, Bearing, RelativeTo> {
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidenceCluster.java
... ...
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.common.DoublePair;
8 8
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
9 9
import com.sap.sse.common.Bearing;
10 10
import com.sap.sse.common.Util;
11
+import com.sap.sse.common.scalablevalue.HasConfidence;
11 12
12 13
/**
13 14
* Contains a number of {@link Bearing} objects and maintains the average bearing. For a given {@link Bearing} it
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/ConfidenceBasedAverager.java
... ...
@@ -2,6 +2,9 @@ package com.sap.sailing.domain.common.confidence;
2 2
3 3
import java.util.Iterator;
4 4
5
+import com.sap.sse.common.scalablevalue.HasConfidence;
6
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
7
+
5 8
public interface ConfidenceBasedAverager<ValueType, BaseType, RelativeTo> {
6 9
/**
7 10
* If a non-<code>null</code> weigher has been set for this averager, <code>at</code> must be a valid reference
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/HasConfidence.java
... ...
@@ -1,91 +0,0 @@
1
-package com.sap.sailing.domain.common.confidence;
2
-
3
-import java.io.Serializable;
4
-
5
-import com.sap.sse.common.Distance;
6
-import com.sap.sse.common.TimePoint;
7
-import com.sap.sse.common.scalablevalue.ScalableValue;
8
-
9
-/**
10
- * Some values, particularly those obtained from real-world measurements, are not always accurate. Some values are
11
- * derived by interpolating or extrapolating data series obtained through measurement or even estimation. Some values
12
- * are simply guessed by humans and entered into the system.
13
- * <p>
14
- *
15
- * All those values have a certain level of confidence. In case multiple sources of information about the same entity or
16
- * phenomenon are available, knowing the confidence of each value helps in weighing and averaging these values more
17
- * properly than would be possible without a confidence value.
18
- * <p>
19
- *
20
- * In simple cases, the type used to compute a weighed average over the things equipped with a confidence level is the
21
- * same as the type of the things themselves. In particular, this is the case for scalar types such as {@link Double}
22
- * and {@link Distance}. For non-scalar values, averaging may be non-trivial. For example, averaging a bearing cannot
23
- * simply happen by computing the arithmetic average of the bearing's angles. Instead, an intermediate structure
24
- * providing the sinus and cosinus values of the bearing's angle is used to compute the weighed average tangens.
25
- * <p>
26
- *
27
- * Generally, the relationship between the type implementing this interface, the <code>ValueType</code> and the
28
- * <code>AveragesTo</code> type is this: an instance of the implementing type can transform itself into a
29
- * {@link ScalableValue} which is then used for computing a weighed sum. The values' weight is their
30
- * {@link #getConfidence() confidence}. The sum (which is still a {@link ScalableValue} because
31
- * {@link ScalableValue#add(ScalableValue)} returns again a {@link ScalableValue}) is then
32
- * {@link ScalableValue#divide(double) divided} by the sum of the confidences. This "division" is expected to
33
- * produce an object of type <code>AveragesTo</code>. Usually, <code>AveragesTo</code> would be the same as the class
34
- * implementing this interface.
35
- *
36
- * @author Axel Uhl (d043530)
37
- *
38
- * @param <ValueType>
39
- * the type of the scalable value used for scalar operations during aggregation
40
- * @param <RelativeTo>
41
- * the type of the object relative to which the confidence applies; for example, if the
42
- * base type is a position and the <code>RelativeTo</code> type is {@link TimePoint},
43
- * the confidence of the position is relative to a certain time point. Together with
44
- * a correspondingly-typed weigher, such a value with confidence can be aggregated with
45
- * other values, again relative to (maybe a different) time point.
46
- */
47
-public interface HasConfidence<ValueType, BaseType, RelativeTo> extends Serializable {
48
- /**
49
- * A confidence is a number between 0.0 and 1.0 (inclusive) where 0.0 means that the value is randomly guessed while
50
- * 1.0 means the value is authoritatively known for a fact. It represents the weight with which a value is to be
51
- * considered by averaging, interpolation and extrapolation algorithms.
52
- * <p>
53
- *
54
- * An averaging algorithm for a sequence of <code>n</code> tuples <code>(v1, c1), ..., (vn, cn)</code> of a value
55
- * <code>vi</code> with a confidence <code>ci</code> each can for example look like this:
56
- * <code>a := (c1*v1 + ... + cn*vn) / (c1 + ... + cn)</code>. For a single value with a confidence this trivially
57
- * results in <code>c1*v1 / (c1)</code> which is equivalent to <code>v1</code>. As another example, consider two
58
- * values with equal confidence <code>0.8</code>. Then, <code>a := (0.8*v1 + 0.8*vn) / (0.8 + 0.8)</code> which
59
- * resolves to <code>0.5*v1 + 0.5*v2</code> which is obviously the arithmetic mean of the two values. If one value
60
- * has confidence <code>0.8</code> and the other <code>0.4</code>, then
61
- * <code>a := (0.8*v1 + 0.4*vn) / (0.8 + 0.4)</code> which resolves to <code>2/3*v1 + 1/3*v2</code> which is a
62
- * weighed average.
63
- * <p>
64
- *
65
- * Note, that this doesn't exactly take facts for facts. In other words, if one value is provided with a confidence
66
- * of <code>1.0</code>, the average may still be influenced by other values. However, this cleanly resolves otherwise
67
- * mutually contradictory "facts" such a <code>(v1, 1.0), (v2, 1.0)</code> with <code>v1 != v2</code>. It is
68
- * considered bad practice to claim a fact as soon as it results from any kind of measurement or estimation. All
69
- * measurement devices produce some statistical errors, no matter how small (cf. Heisenberg ;-) ).
70
- */
71
- double getConfidence();
72
-
73
- /**
74
- * The confidence attached to a value is usually relative to some reference point, such as a time point or
75
- * a position. For example, when a <code>GPSFixTrack</code> is asked to deliver an estimation for the tracked item's
76
- * {@link Position} at some given {@link TimePoint}, the track computes some average from a number of GPS fixes. The resulting
77
- * position has a certain confidence, depending on the time differences between the fixes and the time point for which
78
- * the position estimation was requested. The result therefore carries this reference time point for which the estimation
79
- * was requested so that when the result is to be used in further estimations it is clear relative to which time point the
80
- * confidence is to be interpreted.<p>
81
- *
82
- * In this context, a single GPS fix is a measurement whose values may also have a confidence attached. This confidence could be
83
- * regarded as relative to the fix's time point.
84
- */
85
- RelativeTo getRelativeTo();
86
-
87
- /**
88
- * The object annotated by a confidence
89
- */
90
- BaseType getObject();
91
-}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/HasConfidenceAndIsScalable.java
... ...
@@ -1,7 +0,0 @@
1
-package com.sap.sailing.domain.common.confidence;
2
-
3
-import com.sap.sse.common.scalablevalue.IsScalable;
4
-
5
-public interface HasConfidenceAndIsScalable<ValueType, BaseType, RelativeTo> extends IsScalable<ValueType, BaseType>,
6
- HasConfidence<ValueType, BaseType, RelativeTo> {
7
-}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerImpl.java
... ...
@@ -3,9 +3,9 @@ package com.sap.sailing.domain.common.confidence.impl;
3 3
import java.util.Iterator;
4 4
5 5
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
6
-import com.sap.sailing.domain.common.confidence.HasConfidence;
7
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
8 6
import com.sap.sailing.domain.common.confidence.Weigher;
7
+import com.sap.sse.common.scalablevalue.HasConfidence;
8
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
9 9
import com.sap.sse.common.scalablevalue.ScalableValue;
10 10
11 11
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/HasConfidenceImpl.java
... ...
@@ -1,6 +1,6 @@
1 1
package com.sap.sailing.domain.common.confidence.impl;
2 2
3
-import com.sap.sailing.domain.common.confidence.HasConfidence;
3
+import com.sap.sse.common.scalablevalue.HasConfidence;
4 4
5 5
public class HasConfidenceImpl<ValueType, BaseType, RelativeTo> implements HasConfidence<ValueType, BaseType, RelativeTo> {
6 6
private static final long serialVersionUID = -1635823148449693024L;
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ScalableDouble.java
... ...
@@ -1,47 +0,0 @@
1
-package com.sap.sailing.domain.common.confidence.impl;
2
-
3
-import com.sap.sse.common.scalablevalue.AbstractScalarValue;
4
-import com.sap.sse.common.scalablevalue.ScalableValue;
5
-
6
-public class ScalableDouble implements AbstractScalarValue<Double> {
7
- private final double value;
8
-
9
- public ScalableDouble(double value) {
10
- this.value = value;
11
- }
12
-
13
- @Override
14
- public ScalableDouble multiply(double factor) {
15
- return new ScalableDouble(factor*getValue());
16
- }
17
-
18
- @Override
19
- public ScalableDouble add(ScalableValue<Double, Double> t) {
20
- return new ScalableDouble(getValue()+t.getValue());
21
- }
22
-
23
- @Override
24
- public Double divide(double divisor) {
25
- return getValue()/divisor;
26
- }
27
-
28
- @Override
29
- public Double getValue() {
30
- return value;
31
- }
32
-
33
- @Override
34
- public double getDistance(Double other) {
35
- return Math.abs(value-other);
36
- }
37
-
38
- @Override
39
- public String toString() {
40
- return Double.valueOf(value).toString();
41
- }
42
-
43
- @Override
44
- public int compareTo(Double o) {
45
- return Double.valueOf(value).compareTo(o);
46
- }
47
-}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ScalableDoubleWithConfidence.java
... ...
@@ -1,35 +0,0 @@
1
-package com.sap.sailing.domain.common.confidence.impl;
2
-
3
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
4
-
5
-public class ScalableDoubleWithConfidence<RelativeTo> extends ScalableDouble implements HasConfidenceAndIsScalable<Double, Double, RelativeTo> {
6
- private static final long serialVersionUID = 1042652394404557792L;
7
- private final double confidence;
8
- private final RelativeTo relativeTo;
9
-
10
- public ScalableDoubleWithConfidence(double d, double confidence, RelativeTo relativeTo) {
11
- super(d);
12
- this.confidence = confidence;
13
- this.relativeTo = relativeTo;
14
- }
15
-
16
- @Override
17
- public Double getObject() {
18
- return getValue();
19
- }
20
-
21
- @Override
22
- public RelativeTo getRelativeTo() {
23
- return relativeTo;
24
- }
25
-
26
- @Override
27
- public double getConfidence() {
28
- return confidence;
29
- }
30
-
31
- @Override
32
- public ScalableDouble getScalableValue() {
33
- return this;
34
- }
35
-}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractSerializationTest.java
... ...
@@ -11,7 +11,7 @@ import java.util.HashMap;
11 11
import com.sap.sailing.domain.base.DomainFactory;
12 12
13 13
public abstract class AbstractSerializationTest {
14
- static <T extends Serializable> T cloneBySerialization(final T s, DomainFactory resolveAgainst) throws IOException, ClassNotFoundException {
14
+ protected static <T extends Serializable> T cloneBySerialization(final T s, DomainFactory resolveAgainst) throws IOException, ClassNotFoundException {
15 15
PipedOutputStream pos = new PipedOutputStream();
16 16
PipedInputStream pis = new PipedInputStream(pos);
17 17
final ObjectOutputStream dos = new ObjectOutputStream(pos);
... ...
@@ -27,7 +27,8 @@ public abstract class AbstractSerializationTest {
27 27
}
28 28
}.start();
29 29
Thread.currentThread().setContextClassLoader(AbstractSerializationTest.class.getClassLoader());
30
- ObjectInputStream dis = resolveAgainst.createObjectInputStreamResolvingAgainstThisFactory(pis, /* resolve listener */ null, /* classLoaderCache */ new HashMap<>());
30
+ ObjectInputStream dis = resolveAgainst == null ? new ObjectInputStream(pis) :
31
+ resolveAgainst.createObjectInputStreamResolvingAgainstThisFactory(pis, /* resolve listener */ null, /* classLoaderCache */ new HashMap<>());
31 32
@SuppressWarnings("unchecked")
32 33
T result = (T) dis.readObject();
33 34
dis.close();
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java
... ...
@@ -16,13 +16,13 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
16 16
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
17 17
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
18 18
import com.sap.sailing.domain.common.confidence.ConfidenceFactory;
19
-import com.sap.sailing.domain.common.confidence.HasConfidence;
20 19
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
21 20
import com.sap.sailing.domain.common.impl.DegreePosition;
22 21
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
23 22
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
24 23
import com.sap.sse.common.Bearing;
25 24
import com.sap.sse.common.impl.DegreeBearingImpl;
25
+import com.sap.sse.common.scalablevalue.HasConfidence;
26 26
27 27
public class BearingTest {
28 28
@Test
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConfidenceTest.java
... ...
@@ -18,11 +18,8 @@ import com.sap.sailing.domain.common.Wind;
18 18
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
19 19
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
20 20
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
21
-import com.sap.sailing.domain.common.confidence.HasConfidence;
22
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
23 21
import com.sap.sailing.domain.common.confidence.Weigher;
24 22
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
25
-import com.sap.sailing.domain.common.confidence.impl.ScalableDoubleWithConfidence;
26 23
import com.sap.sailing.domain.common.confidence.impl.ScalableWind;
27 24
import com.sap.sailing.domain.common.impl.DegreePosition;
28 25
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
... ...
@@ -39,6 +36,9 @@ import com.sap.sse.common.Bearing;
39 36
import com.sap.sse.common.TimePoint;
40 37
import com.sap.sse.common.impl.DegreeBearingImpl;
41 38
import com.sap.sse.common.impl.MillisecondsTimePoint;
39
+import com.sap.sse.common.scalablevalue.HasConfidence;
40
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
41
+import com.sap.sse.common.scalablevalue.ScalableDoubleWithConfidence;
42 42
import com.sap.sse.common.scalablevalue.ScalableValue;
43 43
44 44
public class ConfidenceTest {
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseChangeBasedTrackApproximationTest.java
... ...
@@ -1,12 +1,16 @@
1 1
package com.sap.sailing.domain.test;
2 2
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertFalse;
3 5
import static org.junit.jupiter.api.Assertions.assertTrue;
4 6
5 7
import org.junit.jupiter.api.BeforeEach;
6 8
import org.junit.jupiter.api.Test;
7 9
10
+import com.sap.sailing.domain.base.BoatClass;
8 11
import com.sap.sailing.domain.base.Competitor;
9 12
import com.sap.sailing.domain.base.CompetitorWithBoat;
13
+import com.sap.sailing.domain.base.impl.BoatClassImpl;
10 14
import com.sap.sailing.domain.common.impl.DegreePosition;
11 15
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
12 16
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
... ...
@@ -15,6 +19,8 @@ import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
15 19
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
16 20
import com.sap.sailing.domain.tracking.impl.CourseChangeBasedTrackApproximation;
17 21
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
22
+import com.sap.sse.common.Duration;
23
+import com.sap.sse.common.Speed;
18 24
import com.sap.sse.common.TimePoint;
19 25
import com.sap.sse.common.Util;
20 26
import com.sap.sse.common.impl.DegreeBearingImpl;
... ...
@@ -24,9 +30,10 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
24 30
public class CourseChangeBasedTrackApproximationTest {
25 31
private DynamicGPSFixTrack<Competitor, GPSFixMoving> track;
26 32
private CourseChangeBasedTrackApproximation approximation;
33
+ private final static BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */true);
27 34
28 35
@BeforeEach
29
- public void setUp() throws InterruptedException {
36
+ public void setUp() throws Exception {
30 37
final CompetitorWithBoat competitor = TrackBasedTest.createCompetitorWithBoat("Someone");
31 38
track = new DynamicGPSFixMovingTrackImpl<Competitor>(competitor,
32 39
/* millisecondsOverWhichToAverage */5000, /* lossless compaction */true);
... ...
@@ -51,7 +58,6 @@ public class CourseChangeBasedTrackApproximationTest {
51 58
for (int i=0; i<20; i++) {
52 59
track.add(next = travel(next, 1000 /*ms*/, 5 /* knots */, 270 /*deg COG*/));
53 60
}
54
-
55 61
Iterable<GPSFixMoving> candidates = approximation.approximate(start.getTimePoint(), next.getTimePoint());
56 62
assertTrue(Util.size(candidates) >= 1);
57 63
for (final GPSFixMoving candidate : candidates) {
... ...
@@ -60,6 +66,62 @@ public class CourseChangeBasedTrackApproximationTest {
60 66
}
61 67
}
62 68
69
+ @Test
70
+ public void testDirectionChangeJustAboveThreshold() {
71
+ final Duration samplingInterval = Duration.ONE_SECOND;
72
+ final double aBitOverMinimumManeuverAngleDegrees = boatClass.getManeuverDegreeAngleThreshold() * 1.2;
73
+ final TimePoint start = TimePoint.of(10000l);
74
+ final Speed speed = new KnotSpeedImpl(5.0);
75
+ GPSFixMoving next = fix(start.asMillis(), 0, 0, speed.getKnots(), 0);
76
+ track.add(next);
77
+ // perform aBitOverMinimumManeuverAngleDegrees within five fixes:
78
+ final int NUMBER_OF_FIXES_FOR_MANEUVER = 5;
79
+ for (int i=0; i<NUMBER_OF_FIXES_FOR_MANEUVER; i++) {
80
+ next = travel(next, samplingInterval.asMillis(), speed.getKnots(), ((double) i+1.0)/((double) NUMBER_OF_FIXES_FOR_MANEUVER) * aBitOverMinimumManeuverAngleDegrees);
81
+ track.add(next);
82
+ }
83
+ final Iterable<GPSFixMoving> oneManeuverCandidate = approximation.approximate(start, start.plus(samplingInterval.times(NUMBER_OF_FIXES_FOR_MANEUVER)));
84
+ assertFalse(Util.isEmpty(oneManeuverCandidate));
85
+ assertEquals(1, Util.size(oneManeuverCandidate));
86
+ }
87
+
88
+ /**
89
+ * See also https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=6209#c5 which talks about FixWindow contents
90
+ * that describe COG changes in different directions across the FixWindow's duration. This way, a COG change just
91
+ * barely exceeding the threshold wouldn't be recognized/emitted as an approximated fix if it is preceded by a COG
92
+ * change in the other direction that does not by itself pass the threshold.
93
+ */
94
+ @Test
95
+ public void testTurningDirectionChangeInSameWindow() {
96
+ final Duration samplingInterval = Duration.ONE_SECOND;
97
+ final double halfMinimumManeuverAngleDegrees = boatClass.getManeuverDegreeAngleThreshold() / 2.0;
98
+ final TimePoint start = TimePoint.of(10000l);
99
+ final Speed speed = new KnotSpeedImpl(5.0);
100
+ double cog = 0.0;
101
+ GPSFixMoving next = fix(start.asMillis(), 0, 0, speed.getKnots(), cog);
102
+ track.add(next);
103
+ // perform halfMinimumManeuverAngleDegrees within five fixes:
104
+ final int NUMBER_OF_FIXES_FOR_NON_MANEUVER = 5;
105
+ for (int i=0; i<NUMBER_OF_FIXES_FOR_NON_MANEUVER; i++) {
106
+ cog -= 1.0/((double) NUMBER_OF_FIXES_FOR_NON_MANEUVER) * halfMinimumManeuverAngleDegrees;
107
+ next = travel(next, samplingInterval.asMillis(), speed.getKnots(), cog);
108
+ track.add(next);
109
+ }
110
+ final Iterable<GPSFixMoving> emptyManeuverCandidates = approximation.approximate(start, start.plus(samplingInterval.times(NUMBER_OF_FIXES_FOR_NON_MANEUVER)));
111
+ assertTrue(Util.isEmpty(emptyManeuverCandidates));
112
+ final double aBitOverMinimumManeuverAngleDegrees = boatClass.getManeuverDegreeAngleThreshold() * 1.2;
113
+ // perform aBitOverMinimumManeuverAngleDegrees within five fixes:
114
+ final int NUMBER_OF_FIXES_FOR_MANEUVER = 5;
115
+ for (int i=0; i<NUMBER_OF_FIXES_FOR_MANEUVER; i++) {
116
+ cog += 1.0/((double) NUMBER_OF_FIXES_FOR_MANEUVER) * aBitOverMinimumManeuverAngleDegrees;
117
+ next = travel(next, samplingInterval.asMillis(), speed.getKnots(), cog);
118
+ track.add(next);
119
+ }
120
+ final Iterable<GPSFixMoving> oneManeuverCandidate = approximation.approximate(start, start.plus(samplingInterval.times(NUMBER_OF_FIXES_FOR_NON_MANEUVER+NUMBER_OF_FIXES_FOR_MANEUVER)));
121
+ assertFalse(Util.isEmpty(oneManeuverCandidate));
122
+ assertEquals(1, Util.size(oneManeuverCandidate));
123
+ }
124
+
63 125
private GPSFixMoving fix(long timepoint, double lat, double lon, double speedInKnots, double cogDeg) {
64 126
return new GPSFixMovingImpl(new DegreePosition(lat, lon), new MillisecondsTimePoint(timepoint), new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(cogDeg)), /* optionalTrueHeading */ null);
65 127
}
... ...
@@ -68,5 +130,4 @@ public class CourseChangeBasedTrackApproximationTest {
68 130
return new GPSFixMovingImpl(fix.getPosition().translateGreatCircle(new DegreeBearingImpl(cogDeg), new KnotSpeedImpl(speedInKnots).travel(new MillisecondsDurationImpl(durationInMillis))),
69 131
fix.getTimePoint().plus(durationInMillis), new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(cogDeg)), /* optionalTrueHeading */ null);
70 132
}
71
-
72 133
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseChangeBasedTrackApproximationWithTracTracDataTest.java
... ...
@@ -0,0 +1,87 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertFalse;
5
+
6
+import java.io.File;
7
+import java.net.MalformedURLException;
8
+import java.net.URI;
9
+import java.net.URISyntaxException;
10
+import java.net.URL;
11
+import java.util.Random;
12
+
13
+import org.junit.jupiter.api.BeforeEach;
14
+import org.junit.jupiter.api.Test;
15
+
16
+import com.sap.sailing.domain.base.Competitor;
17
+import com.sap.sailing.domain.base.CompetitorWithBoat;
18
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19
+import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
20
+import com.sap.sailing.domain.tracking.impl.CourseChangeBasedTrackApproximation;
21
+import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
22
+import com.sap.sailing.domain.tractracadapter.ReceiverType;
23
+import com.sap.sse.common.TimePoint;
24
+import com.sap.sse.common.Util;
25
+
26
+public class CourseChangeBasedTrackApproximationWithTracTracDataTest extends OnlineTracTracBasedTest {
27
+ private Iterable<Competitor> competitors;
28
+ private CompetitorWithBoat sampleCompetitor;
29
+ private DynamicGPSFixTrack<Competitor, GPSFixMoving> sampleTrack;
30
+
31
+ public CourseChangeBasedTrackApproximationWithTracTracDataTest() throws MalformedURLException, URISyntaxException {
32
+ super();
33
+ }
34
+
35
+ @BeforeEach
36
+ public void setUp() throws Exception {
37
+ super.setUp();
38
+ URI storedUri = new URI("file:///" + new File("resources/event_20110609_KielerWoch-505_Race_2.mtb").getCanonicalPath().replace('\\', '/'));
39
+ super.setUp(
40
+ new URL("file:///" + new File("resources/event_20110609_KielerWoch-505_Race_2.txt").getCanonicalPath()),
41
+ /* liveUri */ null, /* storedUri */ storedUri,
42
+ new ReceiverType[] { ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS });
43
+ getTrackedRace().waitUntilNotLoading();
44
+ assertFalse(Util.isEmpty(getTrackedRace().getRace().getCompetitors()));
45
+ do {
46
+ competitors = getTrackedRace().getRace().getCompetitors();
47
+ sampleCompetitor = (CompetitorWithBoat) Util.get(competitors, new Random().nextInt(Util.size(competitors)));
48
+ sampleTrack = getTrackedRace().getTrack(sampleCompetitor);
49
+ } while (sampleTrack.isEmpty());
50
+ }
51
+
52
+ /**
53
+ * During the work on bug5959 (https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=5959) we identified an issue
54
+ * with early vs. late initialization of the approximation. When initializing the
55
+ * {@link CourseChangeBasedTrackApproximation} objects before adding any fixes to the track, we received different
56
+ * results than when initializing it after adding fixes and first trying to compute maneuvers. This goes against the
57
+ * specification that the approximation should be independent of when it is initialized.<p>
58
+ *
59
+ * To conduct the test, we take the fixes from a loaded competitor track, create a new {@link DynamicGPSFixMovingTrackImpl},
60
+ * construct a {@link CourseChangeBasedTrackApproximation} based on this track (early initialization), then copy all fixes
61
+ * from the loaded competitor track to the new test track (which adds these fixes to the approximation), then create a second
62
+ * {@link CourseChangeBasedTrackApproximation} which will then add all fixes in one sweep. Then, we compare the results of
63
+ * {@link CourseChangeBasedTrackApproximation#approximate(TimePoint, TimePoint)} for the duration of the entire track,
64
+ * expecting them to be equal.
65
+ */
66
+ @Test
67
+ public void testNoDiffBetweenEarlyAndLateInitialization() {
68
+ final DynamicGPSFixTrack<Competitor, GPSFixMoving> trackCopy = new DynamicGPSFixMovingTrackImpl<Competitor>(sampleCompetitor, /* millisecondsOverWhichToAverage */ 15000);
69
+ final CourseChangeBasedTrackApproximation earlyInitApproximation = new CourseChangeBasedTrackApproximation(trackCopy, sampleCompetitor.getBoat().getBoatClass());
70
+ final TimePoint from = sampleTrack.getFirstRawFix().getTimePoint();
71
+ final TimePoint to = sampleTrack.getLastRawFix().getTimePoint();
72
+ sampleTrack.lockForRead();
73
+ try {
74
+ for (final GPSFixMoving fix : sampleTrack.getRawFixes()) {
75
+ trackCopy.add(fix);
76
+ }
77
+ } finally {
78
+ sampleTrack.unlockAfterRead();
79
+ }
80
+ final CourseChangeBasedTrackApproximation lateInitApproximation = new CourseChangeBasedTrackApproximation(trackCopy, sampleCompetitor.getBoat().getBoatClass());
81
+ assertEquals(earlyInitApproximation.getNumberOfFixesAdded(), lateInitApproximation.getNumberOfFixesAdded(), "Number of fixes added to approximators differs");
82
+ final Iterable<GPSFixMoving> earlyInitResult = earlyInitApproximation.approximate(from, to);
83
+ final Iterable<GPSFixMoving> lateInitResult = lateInitApproximation.approximate(from, to);
84
+ assertEquals(Util.size(earlyInitResult), Util.size(lateInitResult), "Different numbers of approximation points for competitor "+sampleCompetitor.getName());
85
+ assertEquals(Util.asSet(earlyInitResult), Util.asSet(lateInitResult));
86
+ }
87
+}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/KMeansTest.java
... ...
@@ -13,7 +13,7 @@ import java.util.Set;
13 13
import org.junit.jupiter.api.Test;
14 14
15 15
import com.sap.sailing.domain.base.ScalableInteger;
16
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
16
+import com.sap.sse.common.scalablevalue.ScalableDouble;
17 17
import com.sap.sse.util.kmeans.Cluster;
18 18
import com.sap.sse.util.kmeans.KMeansClusterer;
19 19
import com.sap.sse.util.kmeans.KMeansClustererWithEquidistantInitialization;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java
... ...
@@ -745,7 +745,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
745 745
}
746 746
747 747
@Override
748
- public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
748
+ public List<GPSFixMoving> approximate(Competitor competitor, TimePoint from, TimePoint to) {
749 749
return null;
750 750
}
751 751
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java
... ...
@@ -371,7 +371,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
371 371
}
372 372
373 373
@Override
374
- public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
374
+ public List<GPSFixMoving> approximate(Competitor competitor, TimePoint from, TimePoint to) {
375 375
return null;
376 376
}
377 377
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/PositionWithConfidence.java
... ...
@@ -1,8 +1,8 @@
1 1
package com.sap.sailing.domain.base;
2 2
3 3
import com.sap.sailing.domain.common.Position;
4
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
5 4
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
5
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
6 6
7 7
/**
8 8
* A position with a confidence attached. The scalable intermediate type represents the x/y/z coordinate of the
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithBearingWithConfidence.java
... ...
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.base;
2 2
3 3
import com.sap.sailing.domain.common.DoubleTriple;
4 4
import com.sap.sailing.domain.common.SpeedWithBearing;
5
-import com.sap.sailing.domain.common.confidence.HasConfidence;
5
+import com.sap.sse.common.scalablevalue.HasConfidence;
6 6
7 7
public interface SpeedWithBearingWithConfidence<RelativeTo> extends
8 8
HasConfidence<DoubleTriple, SpeedWithBearing, RelativeTo> {
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java
... ...
@@ -1,7 +1,7 @@
1 1
package com.sap.sailing.domain.base;
2 2
3
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
4 3
import com.sap.sse.common.Speed;
4
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
5 5
6 6
public interface SpeedWithConfidence<RelativeTo> extends HasConfidenceAndIsScalable<Double, Speed, RelativeTo> {
7 7
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedWindAverager.java
... ...
@@ -2,9 +2,9 @@ package com.sap.sailing.domain.confidence;
2 2
3 3
import com.sap.sailing.domain.common.Wind;
4 4
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
5
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
6 5
import com.sap.sailing.domain.common.confidence.impl.ScalableWind;
7 6
import com.sap.sailing.domain.tracking.WindWithConfidence;
7
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
8 8
9 9
public interface ConfidenceBasedWindAverager<RelativeTo> extends ConfidenceBasedAverager<ScalableWind, Wind, RelativeTo>{
10 10
@Override
java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedWindAveragerImpl.java
... ...
@@ -5,7 +5,6 @@ import java.util.Iterator;
5 5
import com.sap.sailing.domain.common.Wind;
6 6
import com.sap.sailing.domain.common.WindSource;
7 7
import com.sap.sailing.domain.common.WindSourceType;
8
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
9 8
import com.sap.sailing.domain.common.confidence.Weigher;
10 9
import com.sap.sailing.domain.common.confidence.impl.ConfidenceBasedAveragerImpl;
11 10
import com.sap.sailing.domain.common.confidence.impl.ScalableWind;
... ...
@@ -15,6 +14,7 @@ import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
15 14
import com.sap.sailing.domain.tracking.WindWithConfidence;
16 15
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
17 16
import com.sap.sse.common.Speed;
17
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
18 18
19 19
/**
20 20
* In order to enable the aggregation of {@link Wind} objects whose {@link WindSource}'s {@link WindSourceType} suggests
java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ApproximatedFixesCalculatorImpl.java
... ...
@@ -19,9 +19,8 @@ public class ApproximatedFixesCalculatorImpl implements ApproximatedFixesCalcula
19 19
@Override
20 20
public Iterable<GPSFixMoving> approximate(TimePoint earliestStart, TimePoint latestEnd) {
21 21
return trackedRace.approximate(competitor,
22
- trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()
23
- .getMaximumDistanceForCourseApproximation(),
24
- earliestStart, latestEnd);
22
+ earliestStart,
23
+ latestEnd);
25 24
}
26 25
27 26
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DummyTrackedRace.java
... ...
@@ -315,7 +315,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
315 315
}
316 316
317 317
@Override
318
- public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
318
+ public List<GPSFixMoving> approximate(Competitor competitor, TimePoint from, TimePoint to) {
319 319
return null;
320 320
}
321 321
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
... ...
@@ -659,7 +659,7 @@ public interface TrackedRace
659 659
* If the precondition that the {@code competitor} must be {@link RaceDefinition#getCompetitors() part of} the
660 660
* {@link #getRace() race} isn't met, a {@code NullPointerException} will result.
661 661
*/
662
- Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
662
+ Iterable<GPSFixMoving> approximate(Competitor competitor, TimePoint from, TimePoint to);
663 663
664 664
/**
665 665
* @return a non-<code>null</code> but perhaps empty list of the maneuvers that <code>competitor</code> performed in
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindWithConfidence.java
... ...
@@ -2,9 +2,9 @@ package com.sap.sailing.domain.tracking;
2 2
3 3
import com.sap.sailing.domain.common.Position;
4 4
import com.sap.sailing.domain.common.Wind;
5
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
6 5
import com.sap.sailing.domain.common.confidence.impl.ScalableWind;
7 6
import com.sap.sse.common.TimePoint;
7
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
8 8
9 9
/**
10 10
* In order to scale a wind value, a specific type is used: {@link ScalableWind}.
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java
... ...
@@ -5,7 +5,6 @@ import java.io.ObjectInputStream;
5 5
import java.io.Serializable;
6 6
import java.util.function.Function;
7 7
8
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
9 8
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance;
10 9
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
11 10
import com.sap.sailing.domain.common.tracking.BravoFix;
... ...
@@ -25,6 +24,7 @@ import com.sap.sse.common.TimePoint;
25 24
import com.sap.sse.common.Util.Pair;
26 25
import com.sap.sse.common.impl.DegreeBearingImpl;
27 26
import com.sap.sse.common.WithID;
27
+import com.sap.sse.common.scalablevalue.ScalableDouble;
28 28
import com.sap.sse.common.scalablevalue.ScalableValue;
29 29
30 30
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseChangeBasedTrackApproximation.java
... ...
@@ -14,6 +14,7 @@ import java.util.TreeSet;
14 14
15 15
import com.sap.sailing.domain.base.BoatClass;
16 16
import com.sap.sailing.domain.base.Competitor;
17
+import com.sap.sailing.domain.common.NauticalSide;
17 18
import com.sap.sailing.domain.common.SpeedWithBearing;
18 19
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19 20
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
... ...
@@ -24,12 +25,34 @@ import com.sap.sailing.domain.tracking.GPSTrackListener;
24 25
import com.sap.sse.common.Duration;
25 26
import com.sap.sse.common.TimePoint;
26 27
import com.sap.sse.common.TimeRange;
28
+import com.sap.sse.common.Util.Pair;
27 29
import com.sap.sse.common.impl.TimeRangeImpl;
30
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinder;
31
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedNodesImpl;
32
+import com.sap.sse.common.scalablevalue.ScalableDouble;
33
+import com.sap.sse.common.scalablevalue.ScalableValueWithDistance;
28 34
29 35
/**
30 36
* Given a {@link GPSFixTrack} containing {@link GPSFixMoving}, an instance of this class finds areas on the track where
31
- * the course changes sufficiently quickly to indicate a relevant maneuver. The basis for this is the
32
- * {@link BoatClass}'s {@link BoatClass#getApproximateManeuverDuration() maneuver duration}.
37
+ * the course changes sufficiently quickly and far to indicate a relevant maneuver. The basis for the duration to
38
+ * consider is the {@link BoatClass}'s {@link BoatClass#getApproximateManeuverDuration() maneuver duration}. The basis
39
+ * for the angular threshold is {@link BoatClass#getManeuverDegreeAngleThreshold()}.
40
+ * <p>
41
+ *
42
+ * Analyzing the track works with a {@link FixWindow analysis window} that contains up to a certain duration worth of
43
+ * GPS fixes, keeping track of their respective {@link SpeedWithBearing} COG/SOG vectors as well as the cumulative
44
+ * course change counted from the beginning of the window up to each of the fixes stored in the window. Course changes
45
+ * within the window are all in the same {@link NauticalSide direction} (so either all to {@link NauticalSide#PORT PORT}
46
+ * or all to {@link NauticalSide#STARBOARD STARBOARD}). When a fix addition leads to a maximum cumulative course change
47
+ * exceeding the threshold, a maneuver candidate is added to {@link #maneuverCandidates}, and the fixes from the start
48
+ * of the window up to the fix where the
49
+ * <p>
50
+ *
51
+ * When a fix is added to the window that at the insertion point leads to a change in COG change direction, all fixes
52
+ * from the beginning of the window up to the fix preceding the one added are removed, so the window now starts with a
53
+ * course change in the other direction. If the fix insert position was not at the end of the window, the course change
54
+ * direction from the fix added to the next fix is also checked for consistency with the first course change direction
55
+ * of the window.
33 56
* <p>
34 57
*
35 58
* Upon construction, all maneuver candidates for all fixes already contained by the {@link GPSFixTrack} are determined.
... ...
@@ -62,9 +85,10 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
62 85
63 86
/**
64 87
* The set needs no special synchronization; all methods on this {@link CourseChangeBasedTrackApproximation} object
65
- * that may mutate it are {@code synchronized} methods.
88
+ * that may read or mutate it are {@code synchronized} methods.
66 89
*/
67 90
private final NavigableSet<GPSFixMoving> maneuverCandidates;
91
+ private int numberOfFixesAdded;
68 92
69 93
/**
70 94
* The fix window consists of the list of fixes, a corresponding list with the course changes at each fix within the
... ...
@@ -84,43 +108,28 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
84 108
* We need to remember the speed / bearing as we saw them when we inserted the fixes into the {@link #window}
85 109
* collection. Based on more fixes getting added to the track, things may change. In particular, fixes that may have
86 110
* had a valid speed when inserted may later have their cached speed/bearing invalidated, and computing it again
87
- * from the track may then yield {@code null}.
111
+ * from the track may then yield {@code null}.<p>
112
+ *
113
+ * TODO bug6209: the above observation regarding changes when later fixes get added is exactly what is causing the bug6209 issues!
88 114
*/
89 115
private final LinkedList<SpeedWithBearing> speedForFixesInWindow;
90 116
91 117
/**
92 118
* one shorter than "window"; {@link #totalCourseChangeFromBeginningOfWindow}{@code [i]} is from
93
- * {@link #window}{@code [i]} to {@link #window}{@code [i+1]}
119
+ * {@link #window}{@code [0]} to {@link #window}{@code [i+1]}
94 120
*/
95
- private final List<Double> totalCourseChangeFromBeginningOfWindow;
121
+ private final KadaneExtremeSubsequenceFinder<Double, Double, ScalableDouble> courseChangeBetweenFixesInWindow;
96 122
97 123
private final double maneuverAngleInDegreesThreshold;
98 124
private Duration windowDuration;
99 125
100
- /**
101
- * The absolute of the value found at index {@link #indexOfMaximumTotalCourseChange} in
102
- * {@link #totalCourseChangeFromBeginningOfWindow}.
103
- */
104
- private double absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees;
105
-
106
- /**
107
- * -1 means undefined because window is empty; otherwise an index into
108
- * {@link #totalCourseChangeFromBeginningOfWindow} such that the absolute
109
- * value at that index is maximal.
110
- */
111
- private int indexOfMaximumTotalCourseChange;
112
-
113 126
FixWindow() {
114 127
this.window = new LinkedList<>();
115 128
this.speedForFixesInWindow = new LinkedList<>();
116
- this.absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = 0;
117
- this.indexOfMaximumTotalCourseChange = -1;
118 129
this.windowDuration = Duration.NULL;
119 130
// use twice the maneuver duration to also catch slowly-executed gybes
120 131
this.maneuverAngleInDegreesThreshold = boatClass.getManeuverDegreeAngleThreshold();
121
- final Duration averageIntervalBetweenRawFixes = track.getAverageIntervalBetweenRawFixes();
122
- this.totalCourseChangeFromBeginningOfWindow = new ArrayList<>(((int) getMaximumWindowLength().divide(
123
- averageIntervalBetweenRawFixes==null?Duration.ONE_SECOND:averageIntervalBetweenRawFixes))+10);
132
+ this.courseChangeBetweenFixesInWindow = new KadaneExtremeSubsequenceFinderLinkedNodesImpl<>();
124 133
}
125 134
126 135
/**
... ...
@@ -153,8 +162,11 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
153 162
GPSFixMoving add(GPSFixMoving next) {
154 163
assert window.isEmpty() || !next.getTimePoint().before(window.peekFirst().getTimePoint());
155 164
final GPSFixMoving result;
156
- final SpeedWithBearing nextSpeed = next.isEstimatedSpeedCached() ? next.getCachedEstimatedSpeed() : track.getEstimatedSpeed(next.getTimePoint());
157
- if (nextSpeed != null) {
165
+ final SpeedWithBearing nextSpeed = /* TODO this was the original code that can depend on fixes newer than next: */ next.isEstimatedSpeedCached() ? next.getCachedEstimatedSpeed() : track.getEstimatedSpeed(next.getTimePoint());
166
+// next.getSpeed();
167
+ int TODO; // TODO bug6209: try without dependency on newer fixes and see if it helps produce equal results for early/late initialization
168
+ if (nextSpeed != null) { // TODO bug6209: this gets messy... if we drop a fix because no estimated speed can be determined for it, and later fix additions may change this, where would we get this fix from again?
169
+ numberOfFixesAdded++;
158 170
int insertPosition = window.size();
159 171
GPSFixMoving previous;
160 172
SpeedWithBearing previousSpeed;
... ...
@@ -177,19 +189,13 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
177 189
// rather expensive ceil/floor search on the track; resort to track.getEstimatedSpeed if not cached
178 190
assert previousSpeed != null; // we wouldn't have added the fix in the previous run if it hadn't had a valid speed
179 191
final double courseChangeBetweenPreviousAndNextInDegrees = previousSpeed.getBearing().getDifferenceTo(nextSpeed.getBearing()).getDegrees();
180
- windowDuration = windowDuration.plus(previous.getTimePoint().until(next.getTimePoint()));
181
- if (totalCourseChangeFromBeginningOfWindow.isEmpty()) {
182
- totalCourseChangeFromBeginningOfWindow.add(courseChangeBetweenPreviousAndNextInDegrees);
183
- absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = Math.abs(courseChangeBetweenPreviousAndNextInDegrees);
184
- indexOfMaximumTotalCourseChange = 0;
192
+ if (insertPosition == window.size()-1) { // if appended to the end, the window duration changes
193
+ windowDuration = windowDuration.plus(previous.getTimePoint().until(next.getTimePoint()));
194
+ }
195
+ if (courseChangeBetweenFixesInWindow.isEmpty()) {
196
+ courseChangeBetweenFixesInWindow.add(new ScalableDouble(courseChangeBetweenPreviousAndNextInDegrees));
185 197
} else {
186
- final double totalCourseChangeFromBeginningOfWindowForCurrentFix = totalCourseChangeFromBeginningOfWindow.get(totalCourseChangeFromBeginningOfWindow.size()-1)
187
- + courseChangeBetweenPreviousAndNextInDegrees;
188
- totalCourseChangeFromBeginningOfWindow.add(totalCourseChangeFromBeginningOfWindowForCurrentFix);
189
- if (Math.abs(totalCourseChangeFromBeginningOfWindowForCurrentFix) > absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees) {
190
- absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = Math.abs(totalCourseChangeFromBeginningOfWindowForCurrentFix);
191
- indexOfMaximumTotalCourseChange = totalCourseChangeFromBeginningOfWindow.size()-1;
192
- }
198
+ courseChangeBetweenFixesInWindow.add(insertPosition-1, new ScalableDouble(courseChangeBetweenPreviousAndNextInDegrees));
193 199
}
194 200
if (windowDuration.compareTo(getMaximumWindowLength()) > 0) {
195 201
result = tryToExtractManeuverCandidate();
... ...
@@ -198,13 +204,13 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
198 204
// otherwise, keep removing fixes from the beginning of the window until the window duration
199 205
// is again at or below the maximum allowed:
200 206
while (windowDuration.compareTo(getMaximumWindowLength()) > 0) {
201
- removeFirst();
207
+ removeFirst(1);
202 208
}
203 209
}
204 210
} else {
205 211
result = null;
206 212
}
207
- assert window.isEmpty() && totalCourseChangeFromBeginningOfWindow.isEmpty() || window.size() == totalCourseChangeFromBeginningOfWindow.size()+1;
213
+ assert window.isEmpty() && courseChangeBetweenFixesInWindow.isEmpty() || window.size() == courseChangeBetweenFixesInWindow.size()+1;
208 214
} else { // the window was empty so far; we added the next fix, but no maneuver can yet be identified in lack of a course change
209 215
result = null;
210 216
}
... ...
@@ -219,7 +225,7 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
219 225
* Tries to extract a maneuver candidate from the current {@link #window}. See {@link #getManeuverCandidate()}.
220 226
* Basically, the maximum course change in the window has to be equal to or exceed the maneuver threshold. If
221 227
* such a candidate is found, all fixes before the candidate as well as the candidate itself are
222
- * {@link #removeFirst() removed} from the {@link #window}, and all invariants are re-established.
228
+ * {@link #removeFirst(int) removed} from the {@link #window}, and all invariants are re-established.
223 229
* <p>
224 230
*
225 231
* Usually, this method will be called by {@link #add(GPSFixMoving)}, but especially after having added the last
... ...
@@ -232,13 +238,12 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
232 238
* was found.
233 239
*/
234 240
GPSFixMoving tryToExtractManeuverCandidate() {
235
- final GPSFixMoving result;
236 241
// analysis window has exceeded the typical maneuver duration for the boat class;
237
- result = getManeuverCandidate();
238
- if (result != null) {
239
- while (!removeFirst().equals(result)); // remove all including the maneuver fix
242
+ final Pair<GPSFixMoving, Integer> candidateAndItsIndex = getManeuverCandidate();
243
+ if (candidateAndItsIndex != null) {
244
+ removeFirst(candidateAndItsIndex.getB()+1); // remove all including the maneuver fix
240 245
}
241
- return result;
246
+ return candidateAndItsIndex == null ? null : candidateAndItsIndex.getA();
242 247
}
243 248
244 249
/**
... ...
@@ -248,34 +253,18 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
248 253
*
249 254
* @return the fix removed from the beginning of this window
250 255
*/
251
- private GPSFixMoving removeFirst() {
256
+ private void removeFirst(int howManyElementsToRemove) {
252 257
assert !window.isEmpty();
253
- final GPSFixMoving removed = window.removeFirst();
254
- speedForFixesInWindow.removeFirst();
255
- windowDuration = window.isEmpty() ? Duration.NULL : windowDuration.minus(removed.getTimePoint().until(window.getFirst().getTimePoint()));
258
+ for (int i=0; i<howManyElementsToRemove; i++) {
259
+ window.removeFirst();
260
+ speedForFixesInWindow.removeFirst();
261
+ }
262
+ windowDuration = window.isEmpty() ? Duration.NULL : window.getFirst().getTimePoint().until(window.getLast().getTimePoint());
256 263
// adjust totalCourseChangeFromBeginningOfWindow by subtracting the first course change from all others
257 264
// and shifting all by one position to the "left"
258
- absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = 0;
259
- if (totalCourseChangeFromBeginningOfWindow.size() <= 1) { // no more than one element left; can't tell any course change
260
- indexOfMaximumTotalCourseChange = -1;
261
- } else {
262
- final double courseChangeOfFirstInDegrees = totalCourseChangeFromBeginningOfWindow.get(0);
263
- for (int i=0; i<totalCourseChangeFromBeginningOfWindow.size()-1; i++) {
264
- // adjust all total course changes by subtracting the
265
- final double totalCourseChangeFromBeginningOfWindowForFixAtIndex = totalCourseChangeFromBeginningOfWindow.get(i+1)-courseChangeOfFirstInDegrees;
266
- if (Math.abs(totalCourseChangeFromBeginningOfWindowForFixAtIndex) > absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees) {
267
- absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = Math.abs(totalCourseChangeFromBeginningOfWindowForFixAtIndex);
268
- indexOfMaximumTotalCourseChange = i;
269
- }
270
- totalCourseChangeFromBeginningOfWindow.set(i, totalCourseChangeFromBeginningOfWindowForFixAtIndex);
271
- }
272
- }
273
- if (!totalCourseChangeFromBeginningOfWindow.isEmpty()) { // only try to remove if not removing last element of window
274
- totalCourseChangeFromBeginningOfWindow.remove(totalCourseChangeFromBeginningOfWindow.size()-1);
275
- } else {
276
- assert window.isEmpty();
265
+ if (!courseChangeBetweenFixesInWindow.isEmpty()) {
266
+ courseChangeBetweenFixesInWindow.removeFirst(howManyElementsToRemove);
277 267
}
278
- return removed;
279 268
}
280 269
281 270
/**
... ...
@@ -287,28 +276,40 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
287 276
* per time.
288 277
* <p>
289 278
* The {@link #window} is left unchanged.
279
+ *
280
+ * @return a pair holding the maneuver candidate fix if one was found, or {@code null} if no candidate was
281
+ * found, as well as the index of that fix within the {@link #window}
290 282
*/
291
- private GPSFixMoving getManeuverCandidate() {
283
+ private Pair<GPSFixMoving, Integer> getManeuverCandidate() {
292 284
final GPSFixMoving result;
285
+ final ScalableValueWithDistance<Double, Double> maxSum = courseChangeBetweenFixesInWindow.getMaxSum();
286
+ final double maximumCourseChangeToStarboard = maxSum == null ? Double.NEGATIVE_INFINITY : maxSum.divide(1);
287
+ final ScalableValueWithDistance<Double, Double> minSum = courseChangeBetweenFixesInWindow.getMinSum();
288
+ final double maximumCourseChangeToPort = minSum == null ? Double.NEGATIVE_INFINITY : -minSum.divide(1);
289
+ final double absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees = Math.max(maximumCourseChangeToStarboard, maximumCourseChangeToPort);
290
+ int indexOfMaximumAbsoluteCourseChangeInCorrectDirection = -1;
293 291
if (absoluteMaximumTotalCourseChangeFromBeginningOfWindowInDegrees >= maneuverAngleInDegreesThreshold) {
294
- final double signumOfMaximumAbsoluteCourseChange = Math.signum(this.totalCourseChangeFromBeginningOfWindow.get(indexOfMaximumTotalCourseChange));
295
- double previousTotalCourseChange = 0;
296
- double maximumAbsoluteCourseChangeInCorrectDirection = -1;
297
- int indexOfMaximumAbsoluteCourseChangeInCorrectDirection = -1;
298
- for (int i=0; i<=indexOfMaximumTotalCourseChange; i++) {
299
- final double currentTotalCourseChange = totalCourseChangeFromBeginningOfWindow.get(i);
300
- final double courseChange = currentTotalCourseChange-previousTotalCourseChange;
301
- if (courseChange*signumOfMaximumAbsoluteCourseChange > maximumAbsoluteCourseChangeInCorrectDirection) {
302
- maximumAbsoluteCourseChangeInCorrectDirection = courseChange*signumOfMaximumAbsoluteCourseChange;
292
+ final int indexOfMaximumTotalCourseChangeStart = maximumCourseChangeToStarboard >= maximumCourseChangeToPort ?
293
+ courseChangeBetweenFixesInWindow.getStartIndexOfMaxSumSequence():
294
+ courseChangeBetweenFixesInWindow.getStartIndexOfMinSumSequence();
295
+ final Iterable<ScalableDouble> maximumCourseChangeSequence = ()->maximumCourseChangeToStarboard >= maximumCourseChangeToPort ?
296
+ courseChangeBetweenFixesInWindow.getSubSequenceWithMaxSum() :
297
+ courseChangeBetweenFixesInWindow.getSubSequenceWithMinSum();
298
+ int i=indexOfMaximumTotalCourseChangeStart;
299
+ double maximumAbsoluteCourseChangeInCorrectDirection = Double.NEGATIVE_INFINITY;
300
+ for (final ScalableDouble courseChange : maximumCourseChangeSequence) {
301
+ final double absoluteCourseChangeInDegrees = courseChange.divide(maximumCourseChangeToStarboard >= maximumCourseChangeToPort ? 1 : -1);
302
+ if (absoluteCourseChangeInDegrees > maximumAbsoluteCourseChangeInCorrectDirection) {
303
+ maximumAbsoluteCourseChangeInCorrectDirection = absoluteCourseChangeInDegrees;
303 304
indexOfMaximumAbsoluteCourseChangeInCorrectDirection = i;
304 305
}
305
- previousTotalCourseChange = currentTotalCourseChange;
306
+ i++;
306 307
}
307 308
result = window.get(indexOfMaximumAbsoluteCourseChangeInCorrectDirection); // pick the fix introducing, not finishing, the highest turn rate
308 309
} else {
309 310
result = null;
310 311
}
311
- return result;
312
+ return result == null ? null : new Pair<>(result, indexOfMaximumAbsoluteCourseChangeInCorrectDirection);
312 313
}
313 314
314 315
/**
... ...
@@ -340,7 +341,7 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
340 341
private synchronized void addAllFixesOfTrack() {
341 342
track.lockForRead();
342 343
try {
343
- for (final GPSFixMoving fix : track.getFixes()) {
344
+ for (final GPSFixMoving fix : track.getRawFixes()) {
344 345
addFix(fix);
345 346
}
346 347
} finally {
... ...
@@ -363,6 +364,10 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
363 364
}
364 365
}
365 366
367
+ public int getNumberOfFixesAdded() {
368
+ return numberOfFixesAdded;
369
+ }
370
+
366 371
/**
367 372
* We want the listener relationship with the track to be serialized, e.g., during initial load / replication
368 373
*/
... ...
@@ -417,7 +422,7 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
417 422
}
418 423
419 424
/**
420
- * Precondition: the caller holds at least a read lock on {@link #maneuverCandidatesLock}
425
+ * Precondition: the caller owns this object's monitor ({@code synchronized})
421 426
*/
422 427
private GPSFixMoving getExistingManeuverCandidateInRange(TimeRange leftPartOfTimeRangeToReScan) {
423 428
final GPSFixMoving firstCandidateAtOrAfterStartOfTimeRange = maneuverCandidates.ceiling(
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java
... ...
@@ -17,7 +17,6 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
17 17
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
18 18
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
19 19
import com.sap.sailing.domain.common.confidence.ConfidenceFactory;
20
-import com.sap.sailing.domain.common.confidence.HasConfidence;
21 20
import com.sap.sailing.domain.common.confidence.Weigher;
22 21
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
23 22
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
... ...
@@ -32,6 +31,7 @@ import com.sap.sse.common.Distance;
32 31
import com.sap.sse.common.Speed;
33 32
import com.sap.sse.common.TimePoint;
34 33
import com.sap.sse.common.impl.DegreeBearingImpl;
34
+import com.sap.sse.common.scalablevalue.HasConfidence;
35 35
36 36
public class DynamicGPSFixMovingTrackImpl<ItemType> extends GPSFixTrackImpl<ItemType, GPSFixMoving> implements DynamicGPSFixTrack<ItemType, GPSFixMoving> {
37 37
private static final Logger logger = Logger.getLogger(DynamicGPSFixMovingTrackImpl.class.getName());
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
... ...
@@ -24,7 +24,6 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
24 24
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
25 25
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
26 26
import com.sap.sailing.domain.common.confidence.ConfidenceFactory;
27
-import com.sap.sailing.domain.common.confidence.HasConfidence;
28 27
import com.sap.sailing.domain.common.confidence.Weigher;
29 28
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
30 29
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
... ...
@@ -55,6 +54,7 @@ import com.sap.sse.common.impl.DegreeBearingImpl;
55 54
import com.sap.sse.common.impl.MillisecondsDurationImpl;
56 55
import com.sap.sse.common.impl.MillisecondsTimePoint;
57 56
import com.sap.sse.common.impl.TimeRangeImpl;
57
+import com.sap.sse.common.scalablevalue.HasConfidence;
58 58
import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
59 59
60 60
public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTrackImpl<ItemType, FixType>
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
... ...
@@ -105,7 +105,6 @@ import com.sap.sailing.domain.common.WindSourceType;
105 105
import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog;
106 106
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
107 107
import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster;
108
-import com.sap.sailing.domain.common.confidence.HasConfidence;
109 108
import com.sap.sailing.domain.common.confidence.Weigher;
110 109
import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
111 110
import com.sap.sailing.domain.common.confidence.impl.HyperbolicTimeDifferenceWeigher;
... ...
@@ -191,6 +190,7 @@ import com.sap.sse.common.Util.Pair;
191 190
import com.sap.sse.common.Util.Triple;
192 191
import com.sap.sse.common.impl.DegreeBearingImpl;
193 192
import com.sap.sse.common.impl.MillisecondsTimePoint;
193
+import com.sap.sse.common.scalablevalue.HasConfidence;
194 194
import com.sap.sse.concurrent.LockUtil;
195 195
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
196 196
import com.sap.sse.shared.util.impl.ApproximateTime;
... ...
@@ -346,8 +346,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
346 346
private final SerializableManeuverCache maneuverCache;
347 347
348 348
/**
349
- * The values of this map are used by the {@link #approximate(Competitor, Distance, TimePoint, TimePoint)} method and
350
- * maintain state to accelerate the {@link #approximate(Competitor, Distance, TimePoint, TimePoint)} method, also in
349
+ * The values of this map are used by the {@link #approximate(Competitor, TimePoint, TimePoint)} method and
350
+ * maintain state to accelerate the {@link #approximate(Competitor, TimePoint, TimePoint)} method, also in
351 351
* live scenarios when the contents of the competitors' {@link #tracks} changes dynamically.
352 352
*/
353 353
private final ConcurrentMap<Competitor, CourseChangeBasedTrackApproximation> maneuverApproximators;
... ...
@@ -2861,16 +2861,16 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
2861 2861
}
2862 2862
2863 2863
@Override
2864
- public Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
2864
+ public Iterable<GPSFixMoving> approximate(Competitor competitor, TimePoint from, TimePoint to) {
2865 2865
return maneuverApproximators.computeIfAbsent(competitor,
2866 2866
c->new CourseChangeBasedTrackApproximation(getTrack(c), race.getBoatOfCompetitor(c).getBoatClass()))
2867 2867
.approximate(from, to);
2868 2868
}
2869 2869
2870
+
2870 2871
private void ensureManeuverCacheIsFilledForAllCompetitors() {
2871 2872
maneuverCache.ensureFilled();
2872 2873
}
2873
-
2874 2874
public void triggerManeuverCacheRecalculationForAllCompetitors() {
2875 2875
if (cachesSuspended) {
2876 2876
triggerManeuverCacheInvalidationForAllCompetitors = true;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
... ...
@@ -211,7 +211,7 @@ public interface SailingService extends RemoteService, RemoteReplicationService
211 211
SwissTimingEventRecordDTO getRacesOfSwissTimingEvent(String eventJsonURL) throws UnauthorizedException, Exception;
212 212
213 213
Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> getDouglasPoints(
214
- RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges, double meters)
214
+ RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges)
215 215
throws NoWindException, UnauthorizedException;
216 216
217 217
Map<CompetitorDTO, List<ManeuverDTO>> getManeuvers(RegattaAndRaceIdentifier raceIdentifier,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
... ...
@@ -222,7 +222,7 @@ public interface SailingServiceAsync extends RemoteReplicationServiceAsync {
222 222
void getRacesOfSwissTimingEvent(String eventJsonUrl, AsyncCallback<SwissTimingEventRecordDTO> asyncCallback);
223 223
224 224
void getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges,
225
- double meters, AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>> callback);
225
+ AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>> callback);
226 226
227 227
void getManeuvers(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges,
228 228
AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>> callback);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
... ...
@@ -2874,26 +2874,25 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
2874 2874
final TimePoint to = new MillisecondsTimePoint(getBoatFix(competitorDTO, timer.getTime()).timepoint);
2875 2875
timeRange.put(competitorDTO, new TimeRangeImpl(from, to, true));
2876 2876
if (settings.isShowDouglasPeuckerPoints()) {
2877
- sailingService.getDouglasPoints(race, timeRange, 3,
2878
- new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>>() {
2879
- @Override
2880
- public void onFailure(Throwable caught) {
2881
- errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
2882
- }
2883
-
2884
- @Override
2885
- public void onSuccess(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> result) {
2886
- lastDouglasPeuckerResult = result;
2887
- if (douglasMarkers != null) {
2888
- removeAllMarkDouglasPeuckerpoints();
2889
- }
2890
- if (!(timer.getPlayState() == PlayStates.Playing)) {
2891
- if (settings.isShowDouglasPeuckerPoints()) {
2892
- showMarkDouglasPeuckerPoints(result);
2893
- }
2894
- }
2877
+ sailingService.getDouglasPoints(race, timeRange, new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>>() {
2878
+ @Override
2879
+ public void onFailure(Throwable caught) {
2880
+ errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
2881
+ }
2882
+
2883
+ @Override
2884
+ public void onSuccess(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> result) {
2885
+ lastDouglasPeuckerResult = result;
2886
+ if (douglasMarkers != null) {
2887
+ removeAllMarkDouglasPeuckerpoints();
2888
+ }
2889
+ if (!(timer.getPlayState() == PlayStates.Playing)) {
2890
+ if (settings.isShowDouglasPeuckerPoints()) {
2891
+ showMarkDouglasPeuckerPoints(result);
2895 2892
}
2896
- });
2893
+ }
2894
+ }
2895
+ });
2897 2896
}
2898 2897
maneuverMarkersAndLossIndicators.getAndShowManeuvers(race, timeRange);
2899 2898
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
... ...
@@ -206,7 +206,6 @@ import com.sap.sailing.domain.common.dto.TrackedRaceDTO;
206 206
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
207 207
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
208 208
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
209
-import com.sap.sailing.domain.common.impl.MeterDistance;
210 209
import com.sap.sailing.domain.common.impl.WindSourceImpl;
211 210
import com.sap.sailing.domain.common.media.MediaTrack;
212 211
import com.sap.sailing.domain.common.orc.ImpliedWindSource;
... ...
@@ -2809,13 +2808,12 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
2809 2808
2810 2809
@Override
2811 2810
public Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> getDouglasPoints(
2812
- RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges, double meters)
2811
+ RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges)
2813 2812
throws NoWindException {
2814 2813
final Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> result = new HashMap<>();
2815 2814
final TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
2816 2815
getSecurityService().checkCurrentUserReadPermission(trackedRace);
2817 2816
if (trackedRace != null) {
2818
- final MeterDistance maxDistance = new MeterDistance(meters);
2819 2817
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
2820 2818
final CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
2821 2819
if (competitorTimeRanges.containsKey(competitorDTO)) {
... ...
@@ -2823,8 +2821,8 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
2823 2821
final GPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack = trackedRace.getTrack(competitor);
2824 2822
// Distance for DouglasPeucker
2825 2823
final TimeRange timeRange = competitorTimeRanges.get(competitorDTO);
2826
- final Iterable<GPSFixMoving> gpsFixApproximation = trackedRace.approximate(competitor, maxDistance,
2827
- timeRange.from(), timeRange.to());
2824
+ final Iterable<GPSFixMoving> gpsFixApproximation = trackedRace.approximate(competitor, timeRange.from(),
2825
+ timeRange.to());
2828 2826
final List<GPSFixDTOWithSpeedWindTackAndLegType> gpsFixDouglasList = new ArrayList<>();
2829 2827
GPSFix fix = null;
2830 2828
for (GPSFix next : gpsFixApproximation) {
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/LandscapeManagementPanel.java
... ...
@@ -1561,7 +1561,7 @@ public class LandscapeManagementPanel extends SimplePanel {
1561 1561
new AsyncCallback<Void>() {
1562 1562
@Override
1563 1563
public void onSuccess(Void result) {
1564
- Notification.notify(stringMessages.unlockedSuccessfully(), NotificationType.SUCCESS);
1564
+ Notification.notify(stringMessages.success(), NotificationType.SUCCESS);
1565 1565
proxiesTableBusy.setBusy(false);
1566 1566
refreshProxiesTable();
1567 1567
}
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/i18n/StringMessages.java
... ...
@@ -172,7 +172,7 @@ com.sap.sse.gwt.adminconsole.StringMessages {
172 172
String successfullyRotatedHttpdLogsOnInstance(String instance);
173 173
String invalidOperationForThisProxy();
174 174
String pleaseProvideNonEmptyNameAndAZ();
175
- String unlockedSuccessfully();
175
+ String success();
176 176
String availabilityZone();
177 177
String runOnExisting();
178 178
String publicIp();
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/i18n/StringMessages.properties
... ...
@@ -161,7 +161,7 @@ rotateHttpdLogs=Rotate httpd logs
161 161
successfullyRotatedHttpdLogsOnInstance=Successfully rotated the Apache Httpd logs on instance: {0}
162 162
invalidOperationForThisProxy=You can''t perform this operation on this instance
163 163
pleaseProvideNonEmptyNameAndAZ=Please provide a non-empty name (UTF-8) and an AZ.
164
-success=success
164
+success=Success
165 165
availabilityZone=Availability Zone
166 166
runOnExisting=Run on an existing, running instance
167 167
publicIp=Public IP address
java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java
... ...
@@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test;
17 17
18 18
import com.sap.sailing.domain.common.ManeuverType;
19 19
import com.sap.sailing.domain.common.Wind;
20
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
21 20
import com.sap.sailing.domain.common.confidence.impl.ScalableWind;
22 21
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
23 22
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
... ...
@@ -27,6 +26,7 @@ import com.sap.sailing.polars.impl.PolarDataServiceImpl;
27 26
import com.sap.sse.common.Bearing;
28 27
import com.sap.sse.common.Util.Pair;
29 28
import com.sap.sse.common.impl.DegreeBearingImpl;
29
+import com.sap.sse.common.scalablevalue.ScalableDouble;
30 30
import com.sap.sse.util.kmeans.Cluster;
31 31
import com.sap.sse.util.kmeans.KMeansMappingClusterer;
32 32
java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/AbstractManeuverBasedWindEstimationTrackImpl.java
... ...
@@ -17,8 +17,6 @@ import com.sap.sailing.domain.common.ManeuverType;
17 17
import com.sap.sailing.domain.common.Wind;
18 18
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
19 19
import com.sap.sailing.domain.common.confidence.ConfidenceFactory;
20
-import com.sap.sailing.domain.common.confidence.HasConfidence;
21
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
22 20
import com.sap.sailing.domain.common.impl.WindImpl;
23 21
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
24 22
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
... ...
@@ -30,6 +28,8 @@ import com.sap.sse.common.Speed;
30 28
import com.sap.sse.common.Util.Pair;
31 29
import com.sap.sse.common.Util.Triple;
32 30
import com.sap.sse.common.impl.DegreeBearingImpl;
31
+import com.sap.sse.common.scalablevalue.HasConfidence;
32
+import com.sap.sse.common.scalablevalue.ScalableDouble;
33 33
import com.sap.sse.util.kmeans.Cluster;
34 34
import com.sap.sse.util.kmeans.KMeansMappingClusterer;
35 35
java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrack.java
... ...
@@ -5,12 +5,12 @@ import java.util.Set;
5 5
import java.util.stream.Stream;
6 6
7 7
import com.sap.sailing.domain.base.BoatClass;
8
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
9 8
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
10 9
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
11 10
import com.sap.sailing.domain.tracking.WindTrack;
12 11
import com.sap.sse.common.Bearing;
13 12
import com.sap.sse.common.Util.Pair;
13
+import com.sap.sse.common.scalablevalue.ScalableDouble;
14 14
import com.sap.sse.util.kmeans.Cluster;
15 15
16 16
public interface ManeuverBasedWindEstimationTrack extends WindTrack {
java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrackImpl.java
... ...
@@ -13,7 +13,6 @@ import com.sap.sailing.domain.base.CompetitorAndBoat;
13 13
import com.sap.sailing.domain.base.Waypoint;
14 14
import com.sap.sailing.domain.base.impl.CompetitorAndBoatImpl;
15 15
import com.sap.sailing.domain.common.ManeuverType;
16
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
17 16
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
18 17
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
19 18
import com.sap.sailing.domain.polars.PolarDataService;
... ...
@@ -26,6 +25,7 @@ import com.sap.sse.common.Util;
26 25
import com.sap.sse.common.Util.Pair;
27 26
import com.sap.sse.common.impl.DegreeBearingImpl;
28 27
import com.sap.sse.common.impl.MillisecondsTimePoint;
28
+import com.sap.sse.common.scalablevalue.ScalableDouble;
29 29
import com.sap.sse.util.kmeans.Cluster;
30 30
31 31
/**
java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverClassificationToHasConfidenceAndIsScalableAdapter.java
... ...
@@ -4,8 +4,8 @@ import java.util.function.Function;
4 4
5 5
import com.sap.sailing.domain.common.ManeuverType;
6 6
import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager;
7
-import com.sap.sailing.domain.common.confidence.HasConfidenceAndIsScalable;
8 7
import com.sap.sailing.domain.polars.PolarDataService;
8
+import com.sap.sse.common.scalablevalue.HasConfidenceAndIsScalable;
9 9
import com.sap.sse.common.scalablevalue.ScalableValue;
10 10
11 11
/**
java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ScalableBearingAndScalableDouble.java
... ...
@@ -1,9 +1,9 @@
1 1
package com.sap.sailing.polars.windestimation;
2 2
3
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
4 3
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
5 4
import com.sap.sse.common.Bearing;
6 5
import com.sap.sse.common.Util.Pair;
6
+import com.sap.sse.common.scalablevalue.ScalableDouble;
7 7
import com.sap.sse.common.scalablevalue.ScalableValue;
8 8
import com.sap.sse.common.scalablevalue.ScalableValueWithDistance;
9 9
java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java
... ...
@@ -26,7 +26,6 @@ import com.sap.sailing.domain.base.SpeedWithConfidence;
26 26
import com.sap.sailing.domain.common.LegType;
27 27
import com.sap.sailing.domain.common.Tack;
28 28
import com.sap.sailing.domain.common.Wind;
29
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
30 29
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
31 30
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
32 31
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
... ...
@@ -43,6 +42,7 @@ import com.sap.sse.common.Bearing;
43 42
import com.sap.sse.common.Speed;
44 43
import com.sap.sse.common.Util.Pair;
45 44
import com.sap.sse.common.impl.DegreeBearingImpl;
45
+import com.sap.sse.common.scalablevalue.ScalableDouble;
46 46
import com.sap.sse.util.kmeans.Cluster;
47 47
48 48
/**
java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTracTrac.java
... ...
@@ -216,7 +216,7 @@ public class PathGeneratorTracTrac extends PathGeneratorBase {
216 216
Wind endWind = trackedRace.getWind(endPosition, endTime);
217 217
this.raceCourse.addLast(new TimedPositionWithSpeedImpl(endTime, endPosition, endWind));
218 218
219
- Iterable<GPSFixMoving> gpsFixes = trackedRace.approximate(competitor, maxDistance, startTime, endTime);
219
+ Iterable<GPSFixMoving> gpsFixes = trackedRace.approximate(competitor, startTime, endTime);
220 220
Iterator<GPSFixMoving> gpsIter = gpsFixes.iterator();
221 221
222 222
while (gpsIter.hasNext()) {
java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRaceTest.java
... ...
@@ -124,19 +124,23 @@ public class IncrementalMstHmmWindEstimationForTrackedRaceTest extends OnlineTra
124 124
new URL("file:///" + new File("resources/event_20110609_KielerWoch-505_Race_2.txt").getCanonicalPath()),
125 125
/* liveUri */ null, /* storedUri */ storedUri,
126 126
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS, ReceiverType.MARKPOSITIONS });
127
- final MillisecondsTimePoint timePointForFixes = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23, 10, 00).getTime());
127
+ final GregorianCalendar cal = new GregorianCalendar(2011, 05, 23, 13, 40);
128
+ cal.setTimeZone(TimeZone.getTimeZone("UTC"));
129
+ final MillisecondsTimePoint timePointForFixes = new MillisecondsTimePoint(cal.getTime());
128 130
final WindSourceWithAdditionalID testWindSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, "Test");
129 131
getTrackedRace().getOrCreateWindTrack(testWindSource).add(
130 132
new WindImpl(new DegreePosition(54.48448470246412, 10.185846456327479),
131
- timePointForFixes, new KnotSpeedWithBearingImpl(12.5, /* to */ new DegreeBearingImpl(60))));
132
- final MillisecondsTimePoint timePointForFixes2 = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23, 10, 30).getTime());
133
+ timePointForFixes, new KnotSpeedWithBearingImpl(9, /* to */ new DegreeBearingImpl(51))));
134
+ cal.set(2011, 05, 23, 14, 6);
135
+ final MillisecondsTimePoint timePointForFixes2 = new MillisecondsTimePoint(cal.getTime());
133 136
getTrackedRace().getOrCreateWindTrack(testWindSource).add(
134 137
new WindImpl(new DegreePosition(54.48448470246412, 10.185846456327479),
135
- timePointForFixes2, new KnotSpeedWithBearingImpl(11.5, /* to */ new DegreeBearingImpl(58))));
136
- final MillisecondsTimePoint timePointForFixes3 = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23, 10, 45).getTime());
138
+ timePointForFixes2, new KnotSpeedWithBearingImpl(11, /* to */ new DegreeBearingImpl(60))));
139
+ cal.set(2011, 05, 23, 14, 35);
140
+ final MillisecondsTimePoint timePointForFixes3 = new MillisecondsTimePoint(cal.getTime());
137 141
getTrackedRace().getOrCreateWindTrack(testWindSource).add(
138 142
new WindImpl(new DegreePosition(54.4844847, 10.1858464),
139
- timePointForFixes3, new KnotSpeedWithBearingImpl(12.1, /* to */ new DegreeBearingImpl(59))));
143
+ timePointForFixes3, new KnotSpeedWithBearingImpl(14, /* to */ new DegreeBearingImpl(58))));
140 144
final PolarDataServiceImpl polarDataService = new PolarDataServiceImpl();
141 145
getTrackedRace().setPolarDataService(polarDataService);
142 146
polarDataService.insertExistingFixes(getTrackedRace());
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/clustering/ManeuverClusteringBasedWindEstimationTrackImpl.java
... ...
@@ -7,7 +7,6 @@ import java.util.stream.Stream;
7 7
8 8
import com.sap.sailing.domain.base.BoatClass;
9 9
import com.sap.sailing.domain.common.ManeuverType;
10
-import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
11 10
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
12 11
import com.sap.sailing.domain.polars.PolarDataService;
13 12
import com.sap.sailing.polars.windestimation.AbstractManeuverBasedWindEstimationTrackImpl;
... ...
@@ -22,6 +21,7 @@ import com.sap.sailing.windestimation.model.exception.ModelOperationException;
22 21
import com.sap.sse.common.Bearing;
23 22
import com.sap.sse.common.Speed;
24 23
import com.sap.sse.common.Util.Pair;
24
+import com.sap.sse.common.scalablevalue.ScalableDouble;
25 25
import com.sap.sse.util.kmeans.Cluster;
26 26
27 27
/**
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/hmm/GraphNodeTransitionProbabilitiesCalculator.java
... ...
@@ -52,7 +52,4 @@ public interface GraphNodeTransitionProbabilitiesCalculator<GL extends GraphLeve
52 52
GL currentLevel);
53 53
54 54
WindCourseRange getWindCourseRangeForManeuverType(ManeuverForEstimation maneuver, ManeuverTypeForClassification maneuverType);
55
-
56
- boolean isPropagateIntersectedWindRangeOfHeadupAndBearAway();
57
-
58 55
}
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/hmm/IntersectedWindRangeBasedTransitionProbabilitiesCalculator.java
... ...
@@ -177,9 +177,4 @@ public class IntersectedWindRangeBasedTransitionProbabilitiesCalculator<GL exten
177 177
protected Duration getDuration(ManeuverForEstimation fromManeuver, ManeuverForEstimation toManeuver) {
178 178
return fromManeuver.getManeuverTimePoint().until(toManeuver.getManeuverTimePoint()).abs();
179 179
}
180
-
181
- public boolean isPropagateIntersectedWindRangeOfHeadupAndBearAway() {
182
- return propagateIntersectedWindRangeOfHeadupAndBearAway;
183
- }
184
-
185 180
}
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -27,6 +27,13 @@
27 27
<ul class="bulletList">
28 28
<li>Extended RabbitMQ channel heartbeat interval to 1h. This will suffice even for
29 29
longer garbage collection cycles that may happen during replica set start-up.</li>
30
+ <li>Using explicit ("manual") RabbitMQ message delivery acknowledgements for initial
31
+ load when starting up a replica, combined with a "prefetch" (basicQos) value of 1.
32
+ This way, when the replica is processing data
33
+ and doesn't read fast enough, the RabbitMQ server will wait for the next message
34
+ confirmation before sending more messages. This avoids socket buffer overflows
35
+ which previously could lead to the RabbitMQ server timing out trying to send
36
+ messages to the replica's channel.</li>
30 37
</ul>
31 38
<h2 class="articleSubheadline">December 2025</h2>
32 39
<ul class="bulletList">
java/com.sap.sse.common.test/META-INF/MANIFEST.MF
... ...
@@ -18,3 +18,4 @@ Require-Bundle: org.mockito.mockito-core;bundle-version="1.9.5",
18 18
net.bytebuddy.byte-buddy-agent;bundle-version="1.14.12"
19 19
Export-Package: com.sap.sse.common.test
20 20
Automatic-Module-Name: com.sap.sse.common.test
21
+Import-Package: com.sap.sse.testutils
java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderLinkedNodesTest.java
... ...
@@ -0,0 +1,12 @@
1
+package com.sap.sse.common.test;
2
+
3
+import org.junit.jupiter.api.BeforeEach;
4
+
5
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedNodesImpl;
6
+
7
+public class KadaneExtremeSubsequenceFinderLinkedNodesTest extends KadaneExtremeSubsequenceFinderTest {
8
+ @BeforeEach
9
+ public void setUp() {
10
+ finder = new KadaneExtremeSubsequenceFinderLinkedNodesImpl<>();
11
+ }
12
+}
java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderTest.java
... ...
@@ -0,0 +1,230 @@
1
+package com.sap.sse.common.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
5
+import static org.junit.jupiter.api.Assertions.assertTrue;
6
+
7
+import java.io.IOException;
8
+import java.util.Arrays;
9
+import java.util.Random;
10
+import java.util.TreeSet;
11
+import java.util.logging.Logger;
12
+
13
+import org.junit.jupiter.api.AfterAll;
14
+import org.junit.jupiter.api.Test;
15
+
16
+import com.sap.sse.common.Util;
17
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinder;
18
+import com.sap.sse.common.scalablevalue.ScalableDouble;
19
+import com.sap.sse.testutils.Measurement;
20
+import com.sap.sse.testutils.MeasurementCase;
21
+import com.sap.sse.testutils.MeasurementXMLFile;
22
+
23
+public abstract class KadaneExtremeSubsequenceFinderTest {
24
+ private static final double EPSILON = 0.00000001;
25
+ private static final Logger logger = Logger.getLogger(KadaneExtremeSubsequenceFinderTest.class.getName());
26
+
27
+ protected KadaneExtremeSubsequenceFinder<Double, Double, ScalableDouble> finder;
28
+ private static Random random = new Random();
29
+ private static final MeasurementXMLFile performanceReport = new MeasurementXMLFile(KadaneExtremeSubsequenceFinderTest.class);
30
+
31
+ @Test
32
+ public void testSimplePositiveSequence() {
33
+ finder.add(new ScalableDouble(1));
34
+ finder.add(new ScalableDouble(2));
35
+ finder.add(new ScalableDouble(3));
36
+ assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
37
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
38
+ assertEquals(2, finder.getEndIndexOfMaxSumSequence());
39
+ }
40
+
41
+ @Test
42
+ public void testSimplePositiveSequenceWithInsertInTheMiddle() {
43
+ finder.add(new ScalableDouble(1));
44
+ finder.add(new ScalableDouble(3));
45
+ finder.add(1, new ScalableDouble(2));
46
+ assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
47
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
48
+ assertEquals(2, finder.getEndIndexOfMaxSumSequence());
49
+ }
50
+
51
+ @Test
52
+ public void testSimpleSequenceWithPositiveAndNegative() {
53
+ finder.add(new ScalableDouble(1));
54
+ finder.add(new ScalableDouble(2));
55
+ finder.add(new ScalableDouble(3));
56
+ finder.add(new ScalableDouble(-4));
57
+ finder.add(new ScalableDouble(5));
58
+ finder.add(new ScalableDouble(6));
59
+ finder.add(new ScalableDouble(-5));
60
+ assertEquals(13.0, finder.getMaxSum().divide(1.0), EPSILON);
61
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
62
+ assertEquals(5, finder.getEndIndexOfMaxSumSequence());
63
+ }
64
+
65
+ @Test
66
+ public void testSimplePositiveSequenceWithLaterNegativeInsertInTheMiddle() {
67
+ finder.add(new ScalableDouble(1));
68
+ finder.add(new ScalableDouble(2));
69
+ finder.add(new ScalableDouble(3));
70
+ finder.add(new ScalableDouble(4));
71
+ finder.add(new ScalableDouble(5));
72
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
73
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
74
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
75
+ finder.add(3, new ScalableDouble(-7));
76
+ assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON);
77
+ assertEquals(4, finder.getStartIndexOfMaxSumSequence());
78
+ assertEquals(5, finder.getEndIndexOfMaxSumSequence());
79
+ assertTrue(Util.equals(Arrays.asList(new ScalableDouble(4), new ScalableDouble(5)), ()->finder.getSubSequenceWithMaxSum()));
80
+ }
81
+
82
+ @Test
83
+ public void testRemoveFromMiddleOfPositiveSequence() {
84
+ finder.add(new ScalableDouble(1));
85
+ finder.add(new ScalableDouble(2));
86
+ finder.add(new ScalableDouble(3));
87
+ finder.add(new ScalableDouble(4));
88
+ finder.add(new ScalableDouble(5));
89
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
90
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
91
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
92
+ finder.remove(2);
93
+ assertEquals(12.0, finder.getMaxSum().divide(1.0), EPSILON);
94
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
95
+ assertEquals(3, finder.getEndIndexOfMaxSumSequence());
96
+ }
97
+
98
+ /**
99
+ * A white-box test for how we remove from the {@link TreeSet} that holds the best max sum sub-sequence end node.
100
+ * Should the wrong node be removed, the structure would become inconsistent and hold a node as "best" that no
101
+ * longer exists.
102
+ */
103
+ @Test
104
+ public void testTwoEquallyGoodPositiveSequencesThenRemovingFromOne() {
105
+ finder.add(new ScalableDouble(1));
106
+ finder.add(new ScalableDouble(2));
107
+ finder.add(new ScalableDouble(3));
108
+ finder.add(new ScalableDouble(4));
109
+ finder.add(new ScalableDouble(5));
110
+ // now a "very negative" one that starts a new positive sequence after it
111
+ finder.add(new ScalableDouble(-100));
112
+ // now the second positive sequence with equal max sum as the first one
113
+ finder.add(new ScalableDouble(1));
114
+ finder.add(new ScalableDouble(2));
115
+ finder.add(new ScalableDouble(3));
116
+ finder.add(new ScalableDouble(4));
117
+ finder.add(new ScalableDouble(5));
118
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
119
+ assertTrue(finder.getStartIndexOfMaxSumSequence() == 0 || finder.getStartIndexOfMaxSumSequence() == 6);
120
+ assertTrue(finder.getEndIndexOfMaxSumSequence() == 4 || finder.getEndIndexOfMaxSumSequence() == 10);
121
+ finder.remove(3);
122
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
123
+ assertEquals(5, finder.getStartIndexOfMaxSumSequence());
124
+ assertEquals(9, finder.getEndIndexOfMaxSumSequence());
125
+ assertTrue(Util.equals(Arrays.asList(new ScalableDouble(1), new ScalableDouble(2), new ScalableDouble(3), new ScalableDouble(4), new ScalableDouble(5)), ()->finder.getSubSequenceWithMaxSum()));
126
+ finder.remove(8); // removes the 4.0 from the second sequence, resulting again in two equal max sum sub-sequences:
127
+ assertEquals(11.0, finder.getMaxSum().divide(1.0), EPSILON);
128
+ assertTrue(finder.getStartIndexOfMaxSumSequence() == 0 || finder.getStartIndexOfMaxSumSequence() == 5);
129
+ assertTrue(finder.getEndIndexOfMaxSumSequence() == 3 || finder.getEndIndexOfMaxSumSequence() == 8);
130
+ finder.remove(7); // removes the 3.0 from the second sequence
131
+ assertEquals(11.0, finder.getMaxSum().divide(1.0), EPSILON);
132
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
133
+ assertEquals(3, finder.getEndIndexOfMaxSumSequence());
134
+ }
135
+
136
+ @Test
137
+ public void testRemoveFromBeginningOfPositiveSequence() {
138
+ finder.add(new ScalableDouble(1));
139
+ finder.add(new ScalableDouble(2));
140
+ finder.add(new ScalableDouble(3));
141
+ finder.add(new ScalableDouble(4));
142
+ finder.add(new ScalableDouble(5));
143
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
144
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
145
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
146
+ finder.remove(0);
147
+ finder.remove(0);
148
+ finder.remove(0);
149
+ assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON);
150
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
151
+ assertEquals(1, finder.getEndIndexOfMaxSumSequence());
152
+ }
153
+
154
+ @Test
155
+ public void performanceTestWithRandomRemove() throws IOException {
156
+ final MeasurementCase performanceMeasurement = performanceReport.addCase("PerformanceTestWithRandomRemove");
157
+ final int NODES = 10000;
158
+ for (int i=0; i<NODES; i++) {
159
+ finder.add(new ScalableDouble(random.nextDouble()-0.5));
160
+ }
161
+ assertEquals(NODES, finder.size());
162
+ finder.resetStats();
163
+ for (int i=0; i<NODES/2; i++) {
164
+ finder.remove(random.nextInt(finder.size()));
165
+ }
166
+ assertEquals(NODES-NODES/2, finder.size());
167
+ performanceMeasurement.addMeasurement(new Measurement("minChangePropagationCount", finder.getAverageMinChangePropagationSteps()));
168
+ performanceMeasurement.addMeasurement(new Measurement("maxChangePropagationCount", finder.getAverageMaxChangePropagationSteps()));
169
+ logger.info("Stats after random remove: " + finder.toString());
170
+ }
171
+
172
+ @Test
173
+ public void performanceTestWithRemoveFromBeginning() throws IOException {
174
+ final MeasurementCase performanceMeasurement = performanceReport.addCase("PerformanceTestWithRemoveFromBeginning");
175
+ final int NODES = 10000;
176
+ for (int i=0; i<NODES; i++) {
177
+ finder.add(new ScalableDouble(random.nextDouble()-0.5));
178
+ }
179
+ assertEquals(NODES, finder.size());
180
+ finder.resetStats();
181
+ for (int i=0; i<NODES/2; i++) {
182
+ finder.remove(0);
183
+ }
184
+ assertEquals(NODES-NODES/2, finder.size());
185
+ performanceMeasurement.addMeasurement(new Measurement("minChangePropagationCount", finder.getAverageMinChangePropagationSteps()));
186
+ performanceMeasurement.addMeasurement(new Measurement("maxChangePropagationCount", finder.getAverageMaxChangePropagationSteps()));
187
+ logger.info("Stats after removing from beginning: " + finder.toString());
188
+ }
189
+
190
+ @Test
191
+ public void performanceTestWithPruneFromBeginning() throws IOException {
192
+ final MeasurementCase performanceMeasurement = performanceReport.addCase("PerformanceTestWithPruneFromBeginning");
193
+ final int NODES = 10000;
194
+ for (int i=0; i<NODES; i++) {
195
+ finder.add(new ScalableDouble(random.nextDouble()-0.5));
196
+ }
197
+ assertEquals(NODES, finder.size());
198
+ finder.resetStats();
199
+ finder.removeFirst(NODES/2);
200
+ assertEquals(NODES-NODES/2, finder.size());
201
+ assertNotEquals(-1, finder.getStartIndexOfMaxSumSequence());
202
+ assertNotEquals(-1, finder.getEndIndexOfMaxSumSequence());
203
+ assertNotEquals(-1, finder.getStartIndexOfMinSumSequence());
204
+ assertNotEquals(-1, finder.getEndIndexOfMinSumSequence());
205
+ performanceMeasurement.addMeasurement(new Measurement("minChangePropagationCount", finder.getAverageMinChangePropagationSteps()));
206
+ performanceMeasurement.addMeasurement(new Measurement("maxChangePropagationCount", finder.getAverageMaxChangePropagationSteps()));
207
+ logger.info("Stats after pruning from beginning: " + finder.toString());
208
+ }
209
+
210
+ @Test
211
+ public void testSingleNegativeNumber() {
212
+ finder.add(new ScalableDouble(1));
213
+ finder.add(new ScalableDouble(2));
214
+ finder.add(new ScalableDouble(3));
215
+ finder.add(new ScalableDouble(4));
216
+ finder.add(new ScalableDouble(-38.001708984375));
217
+ finder.removeFirst(4);
218
+ assertEquals(-38.001708984375, finder.getMaxSum().divide(1.0), EPSILON);
219
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
220
+ assertEquals(0, finder.getEndIndexOfMaxSumSequence());
221
+ assertEquals(-38.001708984375, finder.getMinSum().divide(1.0), EPSILON);
222
+ assertEquals(0, finder.getStartIndexOfMinSumSequence());
223
+ assertEquals(0, finder.getEndIndexOfMinSumSequence());
224
+ }
225
+
226
+ @AfterAll
227
+ public static void writeMeasurements() throws IOException {
228
+ performanceReport.write();
229
+ }
230
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/HasConfidence.java
... ...
@@ -0,0 +1,90 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.Serializable;
4
+
5
+import com.sap.sse.common.Distance;
6
+import com.sap.sse.common.TimePoint;
7
+
8
+/**
9
+ * Some values, particularly those obtained from real-world measurements, are not always accurate. Some values are
10
+ * derived by interpolating or extrapolating data series obtained through measurement or even estimation. Some values
11
+ * are simply guessed by humans and entered into the system.
12
+ * <p>
13
+ *
14
+ * All those values have a certain level of confidence. In case multiple sources of information about the same entity or
15
+ * phenomenon are available, knowing the confidence of each value helps in weighing and averaging these values more
16
+ * properly than would be possible without a confidence value.
17
+ * <p>
18
+ *
19
+ * In simple cases, the type used to compute a weighed average over the things equipped with a confidence level is the
20
+ * same as the type of the things themselves. In particular, this is the case for scalar types such as {@link Double}
21
+ * and {@link Distance}. For non-scalar values, averaging may be non-trivial. For example, averaging a bearing cannot
22
+ * simply happen by computing the arithmetic average of the bearing's angles. Instead, an intermediate structure
23
+ * providing the sinus and cosinus values of the bearing's angle is used to compute the weighed average tangens.
24
+ * <p>
25
+ *
26
+ * Generally, the relationship between the type implementing this interface, the <code>ValueType</code> and the
27
+ * <code>AveragesTo</code> type is this: an instance of the implementing type can transform itself into a
28
+ * {@link ScalableValue} which is then used for computing a weighed sum. The values' weight is their
29
+ * {@link #getConfidence() confidence}. The sum (which is still a {@link ScalableValue} because
30
+ * {@link ScalableValue#add(ScalableValue)} returns again a {@link ScalableValue}) is then
31
+ * {@link ScalableValue#divide(double) divided} by the sum of the confidences. This "division" is expected to
32
+ * produce an object of type <code>AveragesTo</code>. Usually, <code>AveragesTo</code> would be the same as the class
33
+ * implementing this interface.
34
+ *
35
+ * @author Axel Uhl (d043530)
36
+ *
37
+ * @param <ValueType>
38
+ * the type of the scalable value used for scalar operations during aggregation
39
+ * @param <RelativeTo>
40
+ * the type of the object relative to which the confidence applies; for example, if the
41
+ * base type is a position and the <code>RelativeTo</code> type is {@link TimePoint},
42
+ * the confidence of the position is relative to a certain time point. Together with
43
+ * a correspondingly-typed weigher, such a value with confidence can be aggregated with
44
+ * other values, again relative to (maybe a different) time point.
45
+ */
46
+public interface HasConfidence<ValueType, BaseType, RelativeTo> extends Serializable {
47
+ /**
48
+ * A confidence is a number between 0.0 and 1.0 (inclusive) where 0.0 means that the value is randomly guessed while
49
+ * 1.0 means the value is authoritatively known for a fact. It represents the weight with which a value is to be
50
+ * considered by averaging, interpolation and extrapolation algorithms.
51
+ * <p>
52
+ *
53
+ * An averaging algorithm for a sequence of <code>n</code> tuples <code>(v1, c1), ..., (vn, cn)</code> of a value
54
+ * <code>vi</code> with a confidence <code>ci</code> each can for example look like this:
55
+ * <code>a := (c1*v1 + ... + cn*vn) / (c1 + ... + cn)</code>. For a single value with a confidence this trivially
56
+ * results in <code>c1*v1 / (c1)</code> which is equivalent to <code>v1</code>. As another example, consider two
57
+ * values with equal confidence <code>0.8</code>. Then, <code>a := (0.8*v1 + 0.8*vn) / (0.8 + 0.8)</code> which
58
+ * resolves to <code>0.5*v1 + 0.5*v2</code> which is obviously the arithmetic mean of the two values. If one value
59
+ * has confidence <code>0.8</code> and the other <code>0.4</code>, then
60
+ * <code>a := (0.8*v1 + 0.4*vn) / (0.8 + 0.4)</code> which resolves to <code>2/3*v1 + 1/3*v2</code> which is a
61
+ * weighed average.
62
+ * <p>
63
+ *
64
+ * Note, that this doesn't exactly take facts for facts. In other words, if one value is provided with a confidence
65
+ * of <code>1.0</code>, the average may still be influenced by other values. However, this cleanly resolves otherwise
66
+ * mutually contradictory "facts" such a <code>(v1, 1.0), (v2, 1.0)</code> with <code>v1 != v2</code>. It is
67
+ * considered bad practice to claim a fact as soon as it results from any kind of measurement or estimation. All
68
+ * measurement devices produce some statistical errors, no matter how small (cf. Heisenberg ;-) ).
69
+ */
70
+ double getConfidence();
71
+
72
+ /**
73
+ * The confidence attached to a value is usually relative to some reference point, such as a time point or
74
+ * a position. For example, when a <code>GPSFixTrack</code> is asked to deliver an estimation for the tracked item's
75
+ * {@link Position} at some given {@link TimePoint}, the track computes some average from a number of GPS fixes. The resulting
76
+ * position has a certain confidence, depending on the time differences between the fixes and the time point for which
77
+ * the position estimation was requested. The result therefore carries this reference time point for which the estimation
78
+ * was requested so that when the result is to be used in further estimations it is clear relative to which time point the
79
+ * confidence is to be interpreted.<p>
80
+ *
81
+ * In this context, a single GPS fix is a measurement whose values may also have a confidence attached. This confidence could be
82
+ * regarded as relative to the fix's time point.
83
+ */
84
+ RelativeTo getRelativeTo();
85
+
86
+ /**
87
+ * The object annotated by a confidence
88
+ */
89
+ BaseType getObject();
90
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/HasConfidenceAndIsScalable.java
... ...
@@ -0,0 +1,5 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+public interface HasConfidenceAndIsScalable<ValueType, BaseType, RelativeTo> extends IsScalable<ValueType, BaseType>,
4
+ HasConfidence<ValueType, BaseType, RelativeTo> {
5
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinder.java
... ...
@@ -0,0 +1,87 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.Serializable;
4
+import java.util.Iterator;
5
+
6
+/**
7
+ * In a sequence of {@link ComparableScalableValueWithDistance} objects, tells the contiguous sub-sequence with the
8
+ * greatest and the contiguous sub-sequence with the least sum, according to the
9
+ * {@link ComparableScalableValueWithDistance#add(ScalableValue) add} and the
10
+ * {@link ComparableScalableValueWithDistance#compareTo(Object) compareTo} methods.
11
+ * <p>
12
+ *
13
+ * The sequence is mutable. In particular, elements can be added at any position, also before the start or after the
14
+ * end, and elements can be removed at least from the beginning of the sequence. Updating the sub-sequences with minimal
15
+ * and maximal sums happens with complexity O(1) when adding to the end of the sequence, so with constant effort
16
+ * regardless the size of the sequence. When inserting into or removing from the sequence at arbitrary positions,
17
+ * constant effort can no longer be guaranteed as changes may need to get propagated onwards to following elements.
18
+ * <p>
19
+ *
20
+ * See also <a href="https://en.wikipedia.org/wiki/Maximum_subarray_problem">here</a> for a description of the
21
+ * algorithm.
22
+ *
23
+ * @author Axel Uhl (d043530)
24
+ *
25
+ */
26
+public interface KadaneExtremeSubsequenceFinder<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
27
+ extends Serializable, Iterable<T> {
28
+ int size();
29
+
30
+ default boolean isEmpty() {
31
+ return size() == 0;
32
+ }
33
+
34
+ void add(int index, T t);
35
+
36
+ default void add(T t) {
37
+ add(size(), t);
38
+ }
39
+
40
+ void remove(int index);
41
+
42
+ void remove(T t);
43
+
44
+ void removeFirst(int i);
45
+
46
+ ScalableValueWithDistance<ValueType, AveragesTo> getMinSum();
47
+
48
+ ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum();
49
+
50
+ Iterator<T> getSubSequenceWithMaxSum();
51
+
52
+ Iterator<T> getSubSequenceWithMinSum();
53
+
54
+ int getStartIndexOfMaxSumSequence();
55
+
56
+ /**
57
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
58
+ * maximal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
59
+ * slightly different from how indices may be handled in some other from/to collection operations.
60
+ */
61
+ int getEndIndexOfMaxSumSequence();
62
+
63
+ int getStartIndexOfMinSumSequence();
64
+
65
+ /**
66
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
67
+ * minimal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
68
+ * slightly different from how indices may be handled in some other from/to collection operations.
69
+ */
70
+ int getEndIndexOfMinSumSequence();
71
+
72
+ /**
73
+ * @return statistics: average number of propagation steps when a change affected a minimum sum sub-sequence
74
+ */
75
+ int getAverageMinChangePropagationSteps();
76
+
77
+ /**
78
+ * @return statistics: average number of propagation steps when a change affected a maximum sum sub-sequence
79
+ */
80
+ int getAverageMaxChangePropagationSteps();
81
+
82
+ /**
83
+ * Resets the statistics on change propagation steps as returned by {@link #getAverageMinChangePropagationSteps()}
84
+ * and {@link #getAverageMaxChangePropagationSteps()}.
85
+ */
86
+ void resetStats();
87
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinderLinkedNodesImpl.java
... ...
@@ -0,0 +1,619 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.IOException;
4
+import java.io.ObjectInputStream;
5
+import java.io.ObjectOutputStream;
6
+import java.io.Serializable;
7
+import java.util.Collections;
8
+import java.util.Iterator;
9
+import java.util.TreeSet;
10
+import java.util.function.BiFunction;
11
+import java.util.function.Consumer;
12
+import java.util.function.Function;
13
+
14
+import com.sap.sse.common.Util;
15
+import com.sap.sse.common.impl.SerializableComparator;
16
+
17
+/**
18
+ * An implementation of Kadane's algorithm for "maximum sub-sequence sum" that works incrementally,
19
+ * allows insertion and removal anywhere in the sequence, and maintains the start/end points of those
20
+ * extreme sum sequences for both, the maximal and the minimal sum. It furthermore supports iteration,
21
+ * also across sub-sequences such as those extreme sum sub-sequences, a {@link #size()} as well as an
22
+ * {@link #isEmpty()} operation.<p>
23
+ *
24
+ * This implementation uses a doubly-linked sequence of {@link Node}s. It is <em>not</em> thread-safe.
25
+ * Callers must ensure that concurrent modifications are properly synchronized.
26
+ *
27
+ * @author Axel Uhl (D043530)
28
+ */
29
+public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
30
+ implements KadaneExtremeSubsequenceFinder<ValueType, AveragesTo, T> {
31
+
32
+ private static final long serialVersionUID = -8986609116472739636L;
33
+
34
+ private transient Node<ValueType, AveragesTo, T> first;
35
+
36
+ private transient Node<ValueType, AveragesTo, T> last;
37
+
38
+ private int size;
39
+
40
+ private final TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum;
41
+
42
+ private final TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum;
43
+
44
+ private int minChangePropagationStepsSum; // for internal stats
45
+
46
+ private int minChangePropagationsCount; // for internal stats
47
+
48
+ private int maxChangePropagationStepsSum; // for internal stats
49
+
50
+ private int maxChangePropagationsCount; // for internal stats
51
+
52
+ private static <ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>> Integer compare(
53
+ final ScalableValueWithDistance<ValueType, AveragesTo> a,
54
+ final ScalableValueWithDistance<ValueType, AveragesTo> b) {
55
+ return a.divide(1).compareTo(b.divide(1));
56
+ }
57
+
58
+ /**
59
+ * Nodes of this type are used to construct a doubly-linked list, with each node holding a reference to the node
60
+ * forming the start of the sequence with the maximum sum.
61
+ *
62
+ * @author Axel Uhl (d043530)
63
+ */
64
+ private static class Node<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>> implements Serializable {
65
+ private static final long serialVersionUID = -2547142048423135013L;
66
+ private static int idCounter = 0;
67
+ private final T value;
68
+ private final int id;
69
+ private transient Node<ValueType, AveragesTo, T> previous;
70
+ private transient Node<ValueType, AveragesTo, T> next;
71
+ private ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere;
72
+ private transient Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere;
73
+ private ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere;
74
+ private transient Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere;
75
+
76
+ private Node(Node<ValueType, AveragesTo, T> previous, Node<ValueType, AveragesTo, T> next, T value) {
77
+ super();
78
+ id = idCounter++;
79
+ this.previous = previous;
80
+ this.next = next;
81
+ this.value = value;
82
+ this.minSumEndingHere = null;
83
+ this.startOfMinSumSubSequenceEndingHere = null;
84
+ this.maxSumEndingHere = null;
85
+ this.startOfMaxSumSubSequenceEndingHere = null;
86
+ }
87
+
88
+ /**
89
+ * A unique ID that can be used, e.g., for disambiguation during sorting, so as to keep two nodes with
90
+ * {@link #getValue} values comparing equal apart.
91
+ */
92
+ private int getId() {
93
+ return id;
94
+ }
95
+
96
+ private Node<ValueType, AveragesTo, T> getPrevious() {
97
+ return previous;
98
+ }
99
+
100
+ private Node<ValueType, AveragesTo, T> getNext() {
101
+ return next;
102
+ }
103
+
104
+ private void setPrevious(Node<ValueType, AveragesTo, T> previous) {
105
+ this.previous = previous;
106
+ }
107
+
108
+ private void setNext(Node<ValueType, AveragesTo, T> next) {
109
+ this.next = next;
110
+ }
111
+
112
+ private T getValue() {
113
+ return value;
114
+ }
115
+
116
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMinSumEndingHere() {
117
+ return minSumEndingHere;
118
+ }
119
+
120
+ private Node<ValueType, AveragesTo, T> getStartOfMinSumSubSequenceEndingHere() {
121
+ return startOfMinSumSubSequenceEndingHere;
122
+ }
123
+
124
+ private void setMinSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere) {
125
+ this.minSumEndingHere = minSumEndingHere;
126
+ }
127
+
128
+ private void setStartOfMinSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere) {
129
+ this.startOfMinSumSubSequenceEndingHere = startOfMinSumSubSequenceEndingHere;
130
+ }
131
+
132
+ private void setMaxSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere) {
133
+ this.maxSumEndingHere = maxSumEndingHere;
134
+ }
135
+
136
+ private void setStartOfMaxSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere) {
137
+ this.startOfMaxSumSubSequenceEndingHere = startOfMaxSumSubSequenceEndingHere;
138
+ }
139
+
140
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMaxSumEndingHere() {
141
+ return maxSumEndingHere;
142
+ }
143
+
144
+ private Node<ValueType, AveragesTo, T> getStartOfMaxSumSubSequenceEndingHere() {
145
+ return startOfMaxSumSubSequenceEndingHere;
146
+ }
147
+
148
+ /**
149
+ * Updates this node's extreme sum values of the sub-sequences ending at this node, considering the values
150
+ * stored in the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the
151
+ * references to the nodes where these sub-sequences start are updated accordingly.
152
+ *
153
+ * @return whether the node has changed during this update; this may mean a change in the min/max sum and/or the
154
+ * the start of the extreme sub-sequence(s) ending at this node. Any such change requires updating
155
+ * {@link #getNext() following nodes} too.
156
+ */
157
+ private boolean updateThisFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum, TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum) {
158
+ final boolean changedByMin = updateMinFromPrevious(nodesOrderedByMinSum);
159
+ final boolean changedByMax = updateMaxFromPrevious(nodesOrderedByMaxSum);
160
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
161
+ return changedByMax || changedByMin;
162
+ }
163
+
164
+ /**
165
+ * Updates this node's min sum values of the sub-sequences ending at this node, considering the values stored in
166
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
167
+ * node where the min sum sub-sequence starts is updated accordingly.
168
+ *
169
+ * @return whether the node's min sum-related properties changed during this update; this may mean a change in
170
+ * the min sum and/or the the start of the min sum sub-sequence(s) ending at this node. Any such change
171
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
172
+ * automatically by calling this method. This method updates only this node.
173
+ */
174
+ private boolean updateMinFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum) {
175
+ return updateThisFromPrevious(Node::getMinSumEndingHere,
176
+ Node::getStartOfMinSumSubSequenceEndingHere, this::setMinSumEndingHere,
177
+ this::setStartOfMinSumSubSequenceEndingHere, (a, b)->compare(b, a), nodesOrderedByMinSum);
178
+ }
179
+
180
+ /**
181
+ * Updates this node's max sum values of the sub-sequences ending at this node, considering the values stored in
182
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
183
+ * node where the max sum sub-sequence starts is updated accordingly.
184
+ *
185
+ * @return whether the node's max sum-related properties changed during this update; this may mean a change in
186
+ * the max sum and/or the the start of the max sum sub-sequence(s) ending at this node. Any such change
187
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
188
+ * automatically by calling this method. This method updates only this node.
189
+ */
190
+ private boolean updateMaxFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum) {
191
+ return updateThisFromPrevious(Node::getMaxSumEndingHere,
192
+ Node::getStartOfMaxSumSubSequenceEndingHere, this::setMaxSumEndingHere,
193
+ this::setStartOfMaxSumSubSequenceEndingHere, KadaneExtremeSubsequenceFinderLinkedNodesImpl::compare, nodesOrderedByMaxSum);
194
+ }
195
+
196
+ private boolean updateThisFromPrevious(Function<Node<ValueType, AveragesTo, T>, ScalableValueWithDistance<ValueType, AveragesTo>> getExtremeSumEndingHere,
197
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> getStartOfExtremeSumSubSequenceEndingHere,
198
+ Consumer<ScalableValueWithDistance<ValueType, AveragesTo>> setExtremeSumEndingHere,
199
+ Consumer<Node<ValueType, AveragesTo, T>> setStartOfExtremeSubSubSequenceEndingHere,
200
+ BiFunction<ScalableValueWithDistance<ValueType, AveragesTo>, ScalableValueWithDistance<ValueType, AveragesTo>, Integer> comparator,
201
+ TreeSet<Node<ValueType, AveragesTo, T>> mapOrderedByExtremeSumEndingHereToUpdate) {
202
+ boolean changed = false;
203
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingHere;
204
+ final Node<ValueType, AveragesTo, T> newStartOfMaxSumSubSequenceEndingHere;
205
+ final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = getPrevious() == null ? null : getValue().add(getExtremeSumEndingHere.apply(getPrevious()));
206
+ if (getPrevious() == null || comparator.apply(getValue(), sumWithMax) >= 0) {
207
+ newMaxSumEndingHere = getValue(); // one-element sum consisting of element at "index" is the maximum
208
+ newStartOfMaxSumSubSequenceEndingHere = this;
209
+ } else {
210
+ newMaxSumEndingHere = sumWithMax;
211
+ newStartOfMaxSumSubSequenceEndingHere = getStartOfExtremeSumSubSequenceEndingHere.apply(getPrevious());
212
+ }
213
+ final ScalableValueWithDistance<ValueType, AveragesTo> oldExtremeSumEndingHere = getExtremeSumEndingHere.apply(this);
214
+ if (!newMaxSumEndingHere.equals(oldExtremeSumEndingHere)) {
215
+ changed = true;
216
+ if (oldExtremeSumEndingHere != null) {
217
+ if (!mapOrderedByExtremeSumEndingHereToUpdate.remove(this)) {
218
+ throw new InternalError("This shouldn't have happened as it means the node was not present in the map, although it should have been");
219
+ }
220
+ }
221
+ setExtremeSumEndingHere.accept(newMaxSumEndingHere);
222
+ if (newMaxSumEndingHere != null) {
223
+ mapOrderedByExtremeSumEndingHereToUpdate.add(this);
224
+ }
225
+ }
226
+ if (newStartOfMaxSumSubSequenceEndingHere != getStartOfExtremeSumSubSequenceEndingHere.apply(this)) {
227
+ changed = true;
228
+ setStartOfExtremeSubSubSequenceEndingHere.accept(newStartOfMaxSumSubSequenceEndingHere);
229
+ }
230
+ return changed;
231
+ }
232
+
233
+ @Override
234
+ public String toString() {
235
+ return "["+getValue()+", "+"max: "+getMaxSumEndingHere()+", min: "+getMinSumEndingHere()+"]";
236
+ }
237
+ }
238
+
239
+ public KadaneExtremeSubsequenceFinderLinkedNodesImpl() {
240
+ this.size = 0;
241
+ this.first = null;
242
+ this.last = null;
243
+ final SerializableComparator<? super Node<ValueType, AveragesTo, T>> idComparator = (n1, n2)->Integer.compare(n1.getId(), n2.getId());
244
+ final SerializableComparator<Node<ValueType, AveragesTo, T>> minSumComparator = (n1, n2)->compare(n1.getMinSumEndingHere(), n2.getMinSumEndingHere());
245
+ final SerializableComparator<Node<ValueType, AveragesTo, T>> maxSumComparator = (n1, n2)->compare(n1.getMaxSumEndingHere(), n2.getMaxSumEndingHere());
246
+ final SerializableComparator<? super Node<ValueType, AveragesTo, T>> minSumOuterComparator = (n1,n2)->(n1==n2?0:minSumComparator.thenComparing(idComparator).compare(n1, n2));
247
+ final SerializableComparator<? super Node<ValueType, AveragesTo, T>> maxSumOuterComparator = (n1,n2)->(n1==n2?0:maxSumComparator.thenComparing(idComparator).compare(n1, n2));
248
+ this.nodesOrderedByMinSum = new TreeSet<>(minSumOuterComparator);
249
+ this.nodesOrderedByMaxSum = new TreeSet<>(maxSumOuterComparator);
250
+ }
251
+
252
+ /**
253
+ * Writes the sequence iteratively instead of by recursion, which would be the default
254
+ * {@link ObjectOutputStream} behavior. The nodes need to be "wired" again by the reading end
255
+ * regarding the {@link #previous} and {@link #next} links based on the order in which the
256
+ * nodes are written to the stream.<p>
257
+ *
258
+ * After the nodes follow two references per node to the start nodes of the extreme sum sub-sequences ending at that node,
259
+ * as those are transient again to avoid recursion, and thus not written by the default serialization process. We can assume
260
+ * that the {@link ObjectOutputStream} already holds those nodes, and so only references to those nodes need to be written.
261
+ */
262
+ private void writeObject(ObjectOutputStream oos) throws IOException {
263
+ oos.defaultWriteObject();
264
+ final Iterable<Node<ValueType, AveragesTo, T>> iterable = this::nodeIterator;
265
+ for (final Node<ValueType, AveragesTo, T> node : iterable) {
266
+ oos.writeObject(node);
267
+ }
268
+ // writing the start nodes of the extreme sum sub-sequences separately, as they are transient and thus not written by the default serialization process;
269
+ // we need to write them separately as otherwise we would lose the references to those nodes, and thus the information about where the extreme sum sub-sequences start
270
+ for (final Node<ValueType, AveragesTo, T> node : iterable) {
271
+ oos.writeObject(node.getStartOfMinSumSubSequenceEndingHere());
272
+ oos.writeObject(node.getStartOfMaxSumSubSequenceEndingHere());
273
+ }
274
+ }
275
+
276
+ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
277
+ ois.defaultReadObject(); // this is expected to read the size field
278
+ Node<ValueType, AveragesTo, T> lastRead = null;
279
+ for (int i=0; i<size; i++) {
280
+ @SuppressWarnings("unchecked")
281
+ final Node<ValueType, AveragesTo, T> node = (Node<ValueType, AveragesTo, T>) ois.readObject();
282
+ node.setPrevious(lastRead);
283
+ if (lastRead != null) {
284
+ lastRead.setNext(node);
285
+ }
286
+ lastRead = node;
287
+ if (first == null) {
288
+ first = lastRead;
289
+ }
290
+ }
291
+ last = lastRead;
292
+ final Iterable<Node<ValueType, AveragesTo, T>> iterable = this::nodeIterator;
293
+ for (final Node<ValueType, AveragesTo, T> node : iterable) {
294
+ @SuppressWarnings("unchecked")
295
+ final Node<ValueType, AveragesTo, T> startOfMinSubSubSequenceEndingHere = (Node<ValueType, AveragesTo, T>) ois.readObject();
296
+ node.setStartOfMinSumSubSequenceEndingHere(startOfMinSubSubSequenceEndingHere);
297
+ @SuppressWarnings("unchecked")
298
+ final Node<ValueType, AveragesTo, T> startOfMaxSubSubSequenceEndingHere = (Node<ValueType, AveragesTo, T>) ois.readObject();
299
+ node.setStartOfMaxSumSubSequenceEndingHere(startOfMaxSubSubSequenceEndingHere);
300
+ }
301
+ }
302
+
303
+ @Override
304
+ public int size() {
305
+ return size;
306
+ }
307
+
308
+ @Override
309
+ public boolean isEmpty() {
310
+ return first == null;
311
+ }
312
+
313
+ private Iterable<Node<ValueType, AveragesTo, T>> getNodeIterable() {
314
+ return this::nodeIterator;
315
+ }
316
+
317
+ private Iterator<Node<ValueType, AveragesTo, T>> nodeIterator() {
318
+ return nodeIterator(first, last);
319
+ }
320
+
321
+ private Iterator<Node<ValueType, AveragesTo, T>> nodeIterator(final Node<ValueType, AveragesTo, T> firstNode, final Node<ValueType, AveragesTo, T> lastNode) {
322
+ return new Iterator<Node<ValueType, AveragesTo, T>>() {
323
+ private Node<ValueType, AveragesTo, T> current = firstNode;
324
+
325
+ @Override
326
+ public boolean hasNext() {
327
+ return current != (lastNode==null ? null : lastNode.getNext());
328
+ }
329
+
330
+ @Override
331
+ public Node<ValueType, AveragesTo, T> next() {
332
+ final Node<ValueType, AveragesTo, T> result = current;
333
+ current = current.getNext();
334
+ return result;
335
+ }
336
+ };
337
+ }
338
+
339
+ @Override
340
+ public Iterator<T> iterator() {
341
+ return Util.map(getNodeIterable(), node->node.getValue()).iterator();
342
+ }
343
+
344
+ @Override
345
+ public void add(int index, T t) {
346
+ if (index < 0 || index > size()) {
347
+ throw new IndexOutOfBoundsException("Trying to add at index "+index+" to a sequence of size "+size());
348
+ }
349
+ final Node<ValueType, AveragesTo, T> node;
350
+ if (isEmpty()) {
351
+ node = new Node<>(/* previous */ null, /* next */ null, t);
352
+ first = node;
353
+ last = node;
354
+ node.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum); // no need to consider changes; it's the only element
355
+ } else {
356
+ final Node<ValueType, AveragesTo, T> nodeBeforeIndex;
357
+ final Node<ValueType, AveragesTo, T> nodeAfterIndex;
358
+ if (index>0) {
359
+ nodeBeforeIndex = getNode(index-1);
360
+ nodeAfterIndex = nodeBeforeIndex.getNext();
361
+ } else if (index<size()) {
362
+ nodeAfterIndex = getNode(index);
363
+ nodeBeforeIndex = nodeAfterIndex.getPrevious();
364
+ } else {
365
+ throw new InternalError("This shouldn't have happened as it means the sequence is empty, and we shouldn't have arrived in this branch");
366
+ }
367
+ node = new Node<>(nodeBeforeIndex, nodeAfterIndex, t);
368
+ if (nodeBeforeIndex != null) {
369
+ nodeBeforeIndex.setNext(node);
370
+ } else {
371
+ first = node;
372
+ }
373
+ if (nodeAfterIndex != null) {
374
+ nodeAfterIndex.setPrevious(node);
375
+ } else {
376
+ last = node;
377
+ }
378
+ node.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum); // manages addition to tree maps
379
+ if (nodeBeforeIndex == null
380
+ || !node.getMaxSumEndingHere().equals(nodeBeforeIndex.getMaxSumEndingHere())
381
+ || !node.getMinSumEndingHere().equals(nodeBeforeIndex.getMinSumEndingHere())
382
+ || node.getStartOfMaxSumSubSequenceEndingHere() != nodeBeforeIndex.getStartOfMaxSumSubSequenceEndingHere()
383
+ || node.getStartOfMinSumSubSequenceEndingHere().equals(nodeBeforeIndex.getStartOfMinSumSubSequenceEndingHere())) {
384
+ // the inserted node differs from the previous node in one of the extreme sums ending at it, and/or
385
+ // regarding where those sub-sequences start, so we need to propagate the updates to subsequent nodes
386
+ propagateChanges(node);
387
+ }
388
+ }
389
+ size++;
390
+ }
391
+
392
+ /**
393
+ * Propagates changes to the {@link Node#getNext() next} node that follows {@code node}, and all further ones up to
394
+ * the end of the collection or until no more changes occur that need propagating. The {@link #nodesOrderedByMinSum}
395
+ * and {@link #nodesOrderedByMaxSum} collections are updated accordingly.
396
+ */
397
+ private void propagateChanges(Node<ValueType, AveragesTo, T> node) {
398
+ boolean changedMin = true;
399
+ boolean changedMax = true;
400
+ propagateChanges(node, changedMin, changedMax);
401
+ }
402
+
403
+ /**
404
+ * Propagates changes to the {@link Node#getNext() next} node that follows {@code node}, and all further ones up to
405
+ * the end of the collection or until no more changes occur that need propagating. The {@link #nodesOrderedByMinSum}
406
+ * and {@link #nodesOrderedByMaxSum} collections are updated accordingly.
407
+ *
408
+ * @param changedMin
409
+ * tells if changes to {@code node}'s minimum sum sub-sequences need propagation
410
+ * @param changedMax
411
+ * tells if changes to {@code node}'s maximum sum sub-sequences need propagation
412
+ */
413
+ private void propagateChanges(Node<ValueType, AveragesTo, T> node, boolean changedMin, boolean changedMax) {
414
+ int minChangeCount = 0;
415
+ int maxChangeCount = 0;
416
+ Node<ValueType, AveragesTo, T> current = node.getNext();
417
+ while ((changedMin || changedMax) && current != null) {
418
+ if (changedMin) {
419
+ minChangeCount++;
420
+ changedMin = current.updateMinFromPrevious(nodesOrderedByMinSum);
421
+ }
422
+ if (changedMax) {
423
+ maxChangeCount++;
424
+ changedMax = current.updateMaxFromPrevious(nodesOrderedByMaxSum);
425
+ }
426
+ current = current.getNext();
427
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
428
+ }
429
+ minChangePropagationStepsSum += minChangeCount;
430
+ minChangePropagationsCount++;
431
+ maxChangePropagationStepsSum += maxChangeCount;
432
+ maxChangePropagationsCount++;
433
+ }
434
+
435
+ private Node<ValueType, AveragesTo, T> getNode(int index) {
436
+ if (index < 0 || index >= size()) {
437
+ throw new IndexOutOfBoundsException("Trying to find node at index "+index+" in a sequence of size "+size());
438
+ }
439
+ final Node<ValueType, AveragesTo, T> result;
440
+ if (isEmpty()) {
441
+ result = null;
442
+ } else {
443
+ if (index > size()/2) { // search from the end
444
+ result = step(last, size()-1-index, Node::getPrevious);
445
+ } else { // search from the beginning
446
+ result = step(first, index, Node::getNext);
447
+ }
448
+ }
449
+ return result;
450
+ }
451
+
452
+ private Node<ValueType, AveragesTo, T> step(Node<ValueType, AveragesTo, T> start, int numberOfSteps,
453
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> stepper) {
454
+ Node<ValueType, AveragesTo, T> current = start;
455
+ for (int i=0; i<numberOfSteps; i++) {
456
+ current = stepper.apply(current);
457
+ }
458
+ return current;
459
+ }
460
+
461
+ @Override
462
+ public void remove(int index) {
463
+ final Node<ValueType, AveragesTo, T> node = getNode(index);
464
+ assert node != null; // otherwise, an IndexOutOfBoundsException should have been thrown
465
+ remove(node);
466
+ }
467
+
468
+ private void remove(Node<ValueType, AveragesTo, T> node) {
469
+ if (node.getPrevious() != null) {
470
+ node.getPrevious().setNext(node.getNext());
471
+ } else {
472
+ assert first == node;
473
+ first = node.getNext();
474
+ }
475
+ if (node.getNext() != null) {
476
+ node.getNext().setPrevious(node.getPrevious());
477
+ final boolean changedMin = node.getPrevious() == null
478
+ || !node.getMinSumEndingHere().equals(node.getPrevious().getMinSumEndingHere())
479
+ || node.getStartOfMinSumSubSequenceEndingHere() != node.getPrevious()
480
+ .getStartOfMinSumSubSequenceEndingHere();
481
+ final boolean changedMax = node.getPrevious() == null
482
+ || !node.getMaxSumEndingHere().equals(node.getPrevious().getMaxSumEndingHere())
483
+ || node.getStartOfMaxSumSubSequenceEndingHere() != node.getPrevious()
484
+ .getStartOfMaxSumSubSequenceEndingHere();
485
+ propagateChanges(node, changedMin, changedMax);
486
+ } else {
487
+ assert last == node;
488
+ last = node.getPrevious();
489
+ }
490
+ nodesOrderedByMinSum.remove(node);
491
+ nodesOrderedByMaxSum.remove(node);
492
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
493
+ size--;
494
+ }
495
+
496
+ @Override
497
+ public void remove(T t) {
498
+ Node<ValueType, AveragesTo, T> node = first;
499
+ while (node != null && !node.getValue().equals(t)) {
500
+ node = node.getNext();
501
+ }
502
+ if (node != null) {
503
+ assert node.getValue().equals(t);
504
+ remove(node);
505
+ }
506
+ }
507
+
508
+ @Override
509
+ public void removeFirst(int howManyNodesToRemove) {
510
+ if (howManyNodesToRemove < 0) {
511
+ throw new IllegalArgumentException("Cannot remove a negative number of nodes: "+howManyNodesToRemove);
512
+ }
513
+ if (howManyNodesToRemove > size()) {
514
+ throw new IllegalArgumentException("Cannot remove more nodes than the sequence currently holds: "+howManyNodesToRemove+">"+size());
515
+ }
516
+ if (howManyNodesToRemove > 0) { // otherwise this is a no-op
517
+ final Node<ValueType, AveragesTo, T> lastNodeToRemove = getNode(howManyNodesToRemove-1);
518
+ first = lastNodeToRemove.getNext();
519
+ Node<ValueType, AveragesTo, T> nodeRemoved = lastNodeToRemove;
520
+ while (nodeRemoved != null) {
521
+ nodesOrderedByMinSum.remove(nodeRemoved);
522
+ nodesOrderedByMaxSum.remove(nodeRemoved);
523
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
524
+ nodeRemoved = nodeRemoved.getPrevious();
525
+ }
526
+ if (first != null) {
527
+ first.setPrevious(null);
528
+ if (first.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum)) {
529
+ propagateChanges(first);
530
+ }
531
+ } else {
532
+ assert howManyNodesToRemove == size();
533
+ last = null;
534
+ }
535
+ size -= howManyNodesToRemove;
536
+ }
537
+ }
538
+
539
+ @Override
540
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
541
+ return isEmpty() ? null : nodesOrderedByMinSum.first().getMinSumEndingHere();
542
+ }
543
+
544
+ @Override
545
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
546
+ return isEmpty() ? null : nodesOrderedByMaxSum.last().getMaxSumEndingHere();
547
+ }
548
+
549
+ @Override
550
+ public int getStartIndexOfMaxSumSequence() {
551
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMaxSum.last().getStartOfMaxSumSubSequenceEndingHere());
552
+ }
553
+
554
+ @Override
555
+ public int getEndIndexOfMaxSumSequence() {
556
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMaxSum.last());
557
+ }
558
+
559
+ @Override
560
+ public int getStartIndexOfMinSumSequence() {
561
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMinSum.first().getStartOfMinSumSubSequenceEndingHere());
562
+ }
563
+
564
+ @Override
565
+ public int getEndIndexOfMinSumSequence() {
566
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMinSum.first());
567
+ }
568
+
569
+ @Override
570
+ public Iterator<T> getSubSequenceWithMaxSum() {
571
+ final Iterator<T> result;
572
+ if (isEmpty()) {
573
+ result = Collections.emptyIterator();
574
+ } else {
575
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
576
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere(), nodeWhereBestMaxSumSubSequenceEnds);
577
+ result = Util.map(nodeIterable, node->node.getValue()).iterator();
578
+ }
579
+ return result;
580
+ }
581
+
582
+ @Override
583
+ public Iterator<T> getSubSequenceWithMinSum() {
584
+ final Iterator<T> result;
585
+ if (isEmpty()) {
586
+ result = Collections.emptyIterator();
587
+ } else {
588
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
589
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere(), nodeWhereBestMinSumSubSequenceEnds);
590
+ result = Util.map(nodeIterable, node->node.getValue()).iterator();
591
+ }
592
+ return result;
593
+ }
594
+
595
+ @Override
596
+ public int getAverageMinChangePropagationSteps() {
597
+ return minChangePropagationsCount == 0 ? 0 : minChangePropagationStepsSum / minChangePropagationsCount;
598
+ }
599
+
600
+ @Override
601
+ public int getAverageMaxChangePropagationSteps() {
602
+ return maxChangePropagationsCount == 0 ? 0 : maxChangePropagationStepsSum / maxChangePropagationsCount;
603
+ }
604
+
605
+ @Override
606
+ public void resetStats() {
607
+ minChangePropagationStepsSum = 0;
608
+ minChangePropagationsCount = 0;
609
+ maxChangePropagationStepsSum = 0;
610
+ maxChangePropagationsCount = 0;
611
+ }
612
+
613
+ @Override
614
+ public String toString() {
615
+ return "KadaneExtremeSubsequenceFinderLinkedNodesImpl [size=" + size
616
+ + ", minChangePropagationStepsAvg=" + (minChangePropagationsCount==0?null:(minChangePropagationStepsSum / minChangePropagationsCount))
617
+ + ", maxChangePropagationStepsAvg=" + (maxChangePropagationsCount==0?null:(maxChangePropagationStepsSum / maxChangePropagationsCount)) + "]";
618
+ }
619
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/ScalableDouble.java
... ...
@@ -0,0 +1,71 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.Serializable;
4
+
5
+public class ScalableDouble implements AbstractScalarValue<Double>, Serializable {
6
+ private static final long serialVersionUID = -354261484569358609L;
7
+ private final double value;
8
+
9
+ public ScalableDouble(double value) {
10
+ this.value = value;
11
+ }
12
+
13
+ @Override
14
+ public ScalableDouble multiply(double factor) {
15
+ return new ScalableDouble(factor*getValue());
16
+ }
17
+
18
+ @Override
19
+ public ScalableDouble add(ScalableValue<Double, Double> t) {
20
+ return new ScalableDouble(getValue()+t.getValue());
21
+ }
22
+
23
+ @Override
24
+ public Double divide(double divisor) {
25
+ return getValue()/divisor;
26
+ }
27
+
28
+ @Override
29
+ public Double getValue() {
30
+ return value;
31
+ }
32
+
33
+ @Override
34
+ public double getDistance(Double other) {
35
+ return Math.abs(value-other);
36
+ }
37
+
38
+ @Override
39
+ public String toString() {
40
+ return Double.valueOf(value).toString();
41
+ }
42
+
43
+ @Override
44
+ public int compareTo(Double o) {
45
+ return Double.valueOf(value).compareTo(o);
46
+ }
47
+
48
+ @Override
49
+ public int hashCode() {
50
+ final int prime = 31;
51
+ int result = 1;
52
+ long temp;
53
+ temp = Double.doubleToLongBits(value);
54
+ result = prime * result + (int) (temp ^ (temp >>> 32));
55
+ return result;
56
+ }
57
+
58
+ @Override
59
+ public boolean equals(Object obj) {
60
+ if (this == obj)
61
+ return true;
62
+ if (obj == null)
63
+ return false;
64
+ if (getClass() != obj.getClass())
65
+ return false;
66
+ ScalableDouble other = (ScalableDouble) obj;
67
+ if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value))
68
+ return false;
69
+ return true;
70
+ }
71
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/ScalableDoubleWithConfidence.java
... ...
@@ -0,0 +1,33 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+public class ScalableDoubleWithConfidence<RelativeTo> extends ScalableDouble implements HasConfidenceAndIsScalable<Double, Double, RelativeTo> {
4
+ private static final long serialVersionUID = 1042652394404557792L;
5
+ private final double confidence;
6
+ private final RelativeTo relativeTo;
7
+
8
+ public ScalableDoubleWithConfidence(double d, double confidence, RelativeTo relativeTo) {
9
+ super(d);
10
+ this.confidence = confidence;
11
+ this.relativeTo = relativeTo;
12
+ }
13
+
14
+ @Override
15
+ public Double getObject() {
16
+ return getValue();
17
+ }
18
+
19
+ @Override
20
+ public RelativeTo getRelativeTo() {
21
+ return relativeTo;
22
+ }
23
+
24
+ @Override
25
+ public double getConfidence() {
26
+ return confidence;
27
+ }
28
+
29
+ @Override
30
+ public ScalableDouble getScalableValue() {
31
+ return this;
32
+ }
33
+}
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/ApacheReverseProxy.java
... ...
@@ -69,10 +69,10 @@ implements com.sap.sse.landscape.Process<RotatingFileBasedLog, MetricsT> {
69 69
* the proxy's configuration.
70 70
*/
71 71
private static final String CONFIG_FILE_EXTENSION = ".conf";
72
- private static final String HOME_REDIRECT_MACRO = "Home-SSL";
73
- private static final String PLAIN_REDIRECT_MACRO = "Plain-SSL";
74
- private static final String EVENT_REDIRECT_MACRO = "Event-SSL";
75
- private static final String SERIES_REDIRECT_MACRO = "Series-SSL";
72
+ private static final String HOME_REDIRECT_MACRO = "Home";
73
+ private static final String PLAIN_REDIRECT_MACRO = "Plain";
74
+ private static final String EVENT_REDIRECT_MACRO = "Event";
75
+ private static final String SERIES_REDIRECT_MACRO = "Series";
76 76
private static final String HOME_ARCHIVE_REDIRECT_MACRO = "Home-ARCHIVE";
77 77
private static final String EVENT_ARCHIVE_REDIRECT_MACRO = "Event-ARCHIVE";
78 78
private static final String SERIES_ARCHIVE_REDIRECT_MACRO = "Series-ARCHIVE";
java/com.sap.sse.replication/src/com/sap/sse/replication/impl/RabbitInputStreamProvider.java
... ...
@@ -39,14 +39,16 @@ public class RabbitInputStreamProvider extends NamedImpl {
39 39
messagesAreWrittenToThis = new PipedOutputStream();
40 40
clientReadsFromThis = new PipedInputStream(messagesAreWrittenToThis);
41 41
final QueueingConsumer messageConsumer = new QueueingConsumer(channel);
42
- channel.basicConsume(queueName, /* auto-ack */ true, messageConsumer);
42
+ channel.basicQos(1); // with "manual" acknowledgement, process one message at a time, avoiding overloading inbound socket
43
+ channel.basicConsume(queueName, /* auto-ack */ false, messageConsumer);
43 44
new Thread(getClass().getSimpleName()) {
44 45
@Override
45 46
public void run() {
46 47
while (true) {
47 48
try {
48
- Delivery delivery = messageConsumer.nextDelivery();
49
+ final Delivery delivery = messageConsumer.nextDelivery();
49 50
byte[] bytesFromMessage = delivery.getBody();
51
+ channel.basicAck(delivery.getEnvelope().getDeliveryTag(), /* multiple */ false);
50 52
if (RabbitOutputStream.startsWithTerminationCommand(bytesFromMessage, bytesFromMessage.length)) {
51 53
if (bytesFromMessage.length == RabbitOutputStream.TERMINATION_COMMAND.length) {
52 54
// received exactly TERMINATION_COMMAND
java/target/status
... ...
@@ -32,7 +32,7 @@ RESPONSE_STATUS_CODE=$(echo $STATUS | head -n 1 | cut -d ' ' -f 2)
32 32
33 33
echo "AVAILABILITY_STATUS: $RESPONSE_STATUS_CODE" >&2
34 34
echo "AVAILABILITY_DETAILS:" >&2
35
-echo "$STATUS" | grep "^{" | jq .
35
+echo "$STATUS" | grep "^{" | jq -C .
36 36
37 37
if [[ $RESPONSE_STATUS_CODE =~ 2.. ]]; then
38 38
echo "Server $SERVER_NAME is available on $SERVER_PORT" >&2
wiki/howto/onboarding.md
... ...
@@ -16,7 +16,7 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements
16 16
- Announcements relevant for developers are posted on [GitHub](https://github.com/SAP/sailing-analytics) in the Discussions tab. In order to get notifications you can subscribe to discussions by clicking on "Watch" in the Repository, then "Custom". In the new Popup select "Discussions" and confirm by clicking "Apply".
17 17
18 18
<img src="/wiki/images/github/GitHubWatch.jpg" style="width: 50%"/>
19
- <img src="/wiki/images/github/GitHubSubscribe.jpg" style="width: 50%"/>
19
+ <img src="/wiki/images/github/GitHubSubscribe.jpg" style="width: 45%"/>
20 20
21 21
- In case you'd like to get access to the external git at `ssh://trac@sapsailing.com/home/trac/git` please send your SSH public key to one of the project maintainers, requesting git access. Make sure to NOT generate the key using Putty. Putty keys don't work reliably under Linux and on Windows/Cygwin environments. Use ssh-keygen in a Cygwin or Linux or MacOS/X environment instead. For further instructions for generating an ssh-key see [GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
22 22
Note: If you want to use the ssh-key in the context of our solution, it can be an RSA or ED25519 format. Example for creating a key: `ssh-keygen -t ed25519 -b 512 -C "test@test.com"`. Make sure to set a non-empty password for your key.