d8899121d0fcf7653bc5ff2984030faf3809090e
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. |