diff --git a/.gitignore b/.gitignore index fffa2131..b63d5f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ +# Top level files to ignore +gradlew +gradlew.bat + # Top level directories to ignore +/gradle /build /classes /bin diff --git a/build.gradle.kts b/build.gradle.kts index c21d5973..2c095026 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,11 +19,20 @@ dependencies { api("net.sf.trove4j:trove4j:3.0.3") api("com.sun.xml.bind:jaxb-impl:4.0.5") - api("us.ihmc:ihmc-commons:0.32.0") - api("us.ihmc:euclid-frame:0.21.0") + api("us.ihmc:ihmc-commons:0.33.0") + api("us.ihmc:euclid-frame:0.22.0") +} + +filtersDependencies { + api(ihmc.sourceSetProject("main")) + api("org.ejml:ejml-ddense:0.39"); } testDependencies { - api("us.ihmc:ihmc-commons-testing:0.32.0") - api("us.ihmc:euclid-test:0.21.0") + api(ihmc.sourceSetProject("main")) + api(ihmc.sourceSetProject("filters")) + + api("us.ihmc:ihmc-commons-testing:0.33.0") + api("us.ihmc:euclid-test:0.22.0") + api("org.apache.commons:commons-math3:3.3") } diff --git a/gradle.properties b/gradle.properties index 514abe04..e6b42517 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ title = IHMC YoVariables -extraSourceSets = ["test"] +extraSourceSets = ["filters", "test"] compositeSearchHeight = 0 excludeFromCompositeBuild = false diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AccelerationLimitedYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AccelerationLimitedYoFrameVector3D.java new file mode 100644 index 00000000..ff2e7293 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AccelerationLimitedYoFrameVector3D.java @@ -0,0 +1,160 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +public class AccelerationLimitedYoFrameVector3D extends YoFrameVector3D +{ + private final DoubleProvider maxRateVariable; + private final DoubleProvider maxAccelerationVariable; + + private final FrameTuple3DReadOnly rawPosition; + private final YoBoolean accelerationLimited; + private final YoBoolean rateLimited; + + private final YoBoolean hasBeenInitialized; + private final YoDouble positionGain; + private final YoDouble velocityGain; + + private final YoFrameVector3D smoothedRate; + private final YoFrameVector3D smoothedAcceleration; + + private final double dt; + + private final FrameVector3D positionError; + private final FrameVector3D acceleration; + + public AccelerationLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, + DoubleProvider maxAcceleration, double dt, FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, maxRate, maxAcceleration, dt, rawPosition, rawPosition.getReferenceFrame()); + } + + public AccelerationLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, + DoubleProvider maxAcceleration, double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, maxAcceleration, dt, null, referenceFrame); + } + public AccelerationLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double maxAcceleration, + double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), + VariableTools.createMaxAccelerationYoDouble(namePrefix, nameSuffix, maxAcceleration, registry), dt, null, referenceFrame); + } + + private AccelerationLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, + DoubleProvider maxAcceleration, double dt, FrameTuple3DReadOnly rawPosition, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + positionError = new FrameVector3D(referenceFrame); + acceleration = new FrameVector3D(referenceFrame); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + if (maxAcceleration == null) + maxAcceleration = VariableTools.createMaxAccelerationYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + this.maxRateVariable = maxRate; + this.maxAccelerationVariable = maxAcceleration; + + this.dt = dt; + + hasBeenInitialized = new YoBoolean(namePrefix + "HasBeenInitialized" + namePrefix, registry); + this.rateLimited = new YoBoolean(namePrefix + "RateLimited" + nameSuffix, registry); + this.accelerationLimited = new YoBoolean(namePrefix + "AccelerationLimited" + nameSuffix, registry); + + smoothedRate = new YoFrameVector3D(namePrefix + "SmoothedRate" + namePrefix, referenceFrame, registry); + smoothedAcceleration = new YoFrameVector3D(namePrefix + "SmoothedAcceleration" + namePrefix, referenceFrame, registry); + + positionGain = new YoDouble(namePrefix + "PositionGain" + namePrefix, registry); + velocityGain = new YoDouble(namePrefix + "VelocityGain" + namePrefix, registry); + + double w0 = 2.0 * Math.PI * 16.0; + double zeta = 1.0; + + setGainsByPolePlacement(w0, zeta); + hasBeenInitialized.set(false); + + this.rawPosition = rawPosition; + + } + + + public void setGainsByPolePlacement(double w0, double zeta) + { + positionGain.set(w0 * w0); + velocityGain.set(2.0 * zeta * w0); + } + + public void update() + { + if (rawPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawPosition); + } + + public void update(FrameTuple3DReadOnly frameVectorUnfiltered) + { + checkReferenceFrameMatch(frameVectorUnfiltered); + update(frameVectorUnfiltered.getX(), frameVectorUnfiltered.getY(), frameVectorUnfiltered.getZ()); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenInitialized.getBooleanValue()) + initialize(xUnfiltered, yUnfiltered, zUnfiltered); + + positionError.set(xUnfiltered, yUnfiltered, zUnfiltered); + positionError.sub(this); + + acceleration.set(smoothedRate); + acceleration.scale(-velocityGain.getValue()); + acceleration.scaleAdd(positionGain.getValue(), positionError, acceleration); + + accelerationLimited.set(acceleration.clipToMaxLength(maxAccelerationVariable.getValue())); + + smoothedAcceleration.set(acceleration); + smoothedRate.scaleAdd(dt, smoothedAcceleration, smoothedRate); + + rateLimited.set(smoothedRate.clipToMaxLength(maxRateVariable.getValue())); + + this.scaleAdd(dt, smoothedRate, this); + + if (this.containsNaN()) + throw new RuntimeException("what?"); + + } + + public void initialize(FrameTuple3DReadOnly input) + { + initialize(input.getX(), input.getY(), input.getZ()); + } + + public void initialize(double xInput, double yInput, double zInput) + { + this.set(xInput, yInput, zInput); + smoothedRate.setToZero(); + smoothedAcceleration.setToZero(); + + this.hasBeenInitialized.set(true); + } + + public void reset() + { + this.hasBeenInitialized.set(false); + smoothedRate.setToZero(); + smoothedAcceleration.setToZero(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransform.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransform.java new file mode 100644 index 00000000..448326c4 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransform.java @@ -0,0 +1,43 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.transform.RigidBodyTransform; + +public class AlphaFilteredRigidBodyTransform extends RigidBodyTransform +{ + // an alpha of zero applies zero filtering, accepting the entirely new input. + private double alpha = 0.0; + private final RigidBodyTransform previousFiltered = new RigidBodyTransform(); + + public AlphaFilteredRigidBodyTransform() + { + reset(); + } + + public void update(RigidBodyTransform measured) + { + if (containsNaN()) + { + set(measured); + } + else + { + previousFiltered.set(this); + interpolate(measured, previousFiltered, alpha); + } + } + + public double getAlpha() + { + return alpha; + } + + public void setAlpha(double alpha) + { + this.alpha = alpha; + } + + public void reset() + { + setToNaN(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple2D.java new file mode 100644 index 00000000..59d059c0 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple2D.java @@ -0,0 +1,108 @@ +package us.ihmc.yoVariables.euclid.filters; + +import org.apache.commons.lang3.NotImplementedException; + +import us.ihmc.euclid.interfaces.EuclidGeometry; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.euclid.transform.interfaces.Transform; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DBasics; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; +import us.ihmc.yoVariables.providers.DoubleProvider; + +public class AlphaFilteredTuple2D implements Tuple2DBasics +{ + private final DoubleProvider alpha; + + private boolean resetX; + private boolean resetY; + + private double x; + private double y; + + public AlphaFilteredTuple2D(DoubleProvider alpha) + { + this.alpha = alpha; + reset(); + } + + public AlphaFilteredTuple2D(Tuple2DReadOnly initialValue, DoubleProvider alpha) + { + this.alpha = alpha; + reset(initialValue); + } + + public void reset() + { + resetX = true; + resetY = true; + } + + public void reset(Tuple2DReadOnly other) + { + reset(other.getX(), other.getY()); + } + + public void reset(double x, double y) + { + reset(); + set(x, y); + } + + @Override + public double getX() + { + return x; + } + + @Override + public double getY() + { + return y; + } + + @Override + public void setX(double x) + { + if (resetX) + { + this.x = x; + resetX = false; + } + else + { + this.x = EuclidCoreTools.interpolate(x, this.x, alpha.getValue()); + } + } + + @Override + public void setY(double y) + { + if (resetY) + { + this.y = y; + resetY = false; + } + else + { + this.y = EuclidCoreTools.interpolate(y, this.y, alpha.getValue()); + } + } + + @Override + public boolean geometricallyEquals(EuclidGeometry geometry, double epsilon) + { + return epsilonEquals(geometry, epsilon); + } + + @Override + public void applyTransform(Transform transform, boolean checkIfTransformInXYPlane) + { + throw new NotImplementedException("Not supported by " + getClass().getSimpleName() + "."); + } + + @Override + public void applyInverseTransform(Transform transform, boolean checkIfTransformInXYPlane) + { + throw new NotImplementedException("Not supported by " + getClass().getSimpleName() + "."); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3D.java new file mode 100644 index 00000000..e2010930 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3D.java @@ -0,0 +1,135 @@ +package us.ihmc.yoVariables.euclid.filters; + +import org.apache.commons.lang3.NotImplementedException; +import us.ihmc.euclid.interfaces.EuclidGeometry; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.euclid.transform.interfaces.Transform; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.providers.DoubleProvider; + +public class AlphaFilteredTuple3D implements Tuple3DBasics +{ + private final DoubleProvider alpha; + + private double x = Double.NaN; + private double y = Double.NaN; + private double z = Double.NaN; + + private boolean resetX = false; + private boolean resetY = false; + private boolean resetZ = false; + + public AlphaFilteredTuple3D(DoubleProvider alpha) + { + this.alpha = alpha; + } + + public AlphaFilteredTuple3D(double x, double y, double z, DoubleProvider alpha) + { + this.alpha = alpha; + reset(x, y, z); + } + + public AlphaFilteredTuple3D(Tuple3DReadOnly other, DoubleProvider alpha) + { + this.alpha = alpha; + reset(other); + } + + public void reset() + { + resetX = true; + resetY = true; + resetZ = true; + } + + public void reset(Tuple3DReadOnly other) + { + reset(other.getX(), other.getY(), other.getZ()); + } + + public void reset(double x, double y, double z) + { + reset(); + set(x, y, z); + } + + @Override + public void setX(double x) + { + if (resetX || Double.isNaN(this.x)) + { + this.x = x; + resetX = false; + } + else + { + this.x = EuclidCoreTools.interpolate(x, this.x, alpha.getValue()); + } + } + + @Override + public void setY(double y) + { + if (resetY || Double.isNaN(this.y)) + { + this.y = y; + resetY = false; + } + else + { + this.y = EuclidCoreTools.interpolate(y, this.y, alpha.getValue()); + } + } + + @Override + public void setZ(double z) + { + if (resetZ || Double.isNaN(this.z)) + { + this.z = z; + resetZ = false; + } + else + { + this.z = EuclidCoreTools.interpolate(z, this.z, alpha.getValue()); + } + } + + @Override + public double getX() + { + return x; + } + + @Override + public double getY() + { + return y; + } + + @Override + public double getZ() + { + return z; + } + + @Override + public boolean geometricallyEquals(EuclidGeometry geometry, double epsilon) + { + return epsilonEquals(geometry, epsilon); + } + + @Override + public void applyTransform(Transform transform) + { + throw new NotImplementedException("Sorry mate, " + getClass().getSimpleName() + " doesn't implement this method."); + } + + @Override + public void applyInverseTransform(Transform transform) + { + throw new NotImplementedException("Sorry mate, " + getClass().getSimpleName() + " doesn't implement this method."); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2D.java new file mode 100644 index 00000000..135fe68b --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2D.java @@ -0,0 +1,92 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple2DReadOnly; +import us.ihmc.euclid.tuple2D.Point2D; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint2D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFramePoint2D extends YoFramePoint2D +{ + private final DoubleProvider alpha; + + private final FrameTuple2DReadOnly unfilteredPoint; + private final YoBoolean hasBeenCalled; + + public AlphaFilteredYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, double alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createAlphaYoDouble(namePrefix, nameSuffix, alpha, registry), referenceFrame); + } + + public AlphaFilteredYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, alpha, referenceFrame, null); + } + + public AlphaFilteredYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + FrameTuple2DReadOnly unfilteredPoint) + { + this(namePrefix, nameSuffix, registry, alpha, unfilteredPoint.getReferenceFrame(), unfilteredPoint); + } + + private AlphaFilteredYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame, + FrameTuple2DReadOnly unfilteredPoint) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.alpha = alpha; + this.unfilteredPoint = unfilteredPoint; + if (unfilteredPoint != null) + checkReferenceFrameMatch(unfilteredPoint); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (unfilteredPoint == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(unfilteredPoint); + } + + public void update(FrameTuple2DReadOnly unfilteredPoint) + { + checkReferenceFrameMatch(unfilteredPoint); + update((Tuple2DReadOnly) unfilteredPoint); + } + + public void update(Tuple2DReadOnly unfilteredPoint) + { + update(unfilteredPoint.getX(), unfilteredPoint.getY()); + } + + private final Point2D unfilteredPoint2D = new Point2D(); + + public void update(double xUnfiltered, double yUnfiltered) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered); + } + else + { + unfilteredPoint2D.set(xUnfiltered, yUnfiltered); + interpolate(unfilteredPoint2D, this, alpha.getValue()); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3D.java new file mode 100644 index 00000000..16ff00eb --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3D.java @@ -0,0 +1,90 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFramePoint3D extends YoFramePoint3D +{ + private final DoubleProvider alpha; + + private final FrameTuple3DReadOnly unfilteredPoint; + private final YoBoolean hasBeenCalled; + + public AlphaFilteredYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, double alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createAlphaYoDouble(namePrefix, nameSuffix, alpha, registry), referenceFrame); + } + + public AlphaFilteredYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, alpha, referenceFrame, null); + } + + public AlphaFilteredYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + FrameTuple3DReadOnly unfilteredPoint) + { + this(namePrefix, nameSuffix, registry, alpha, unfilteredPoint.getReferenceFrame(), unfilteredPoint); + } + + private AlphaFilteredYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame, + FrameTuple3DReadOnly unfilteredPoint) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.alpha = alpha; + this.unfilteredPoint = unfilteredPoint; + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (unfilteredPoint == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(unfilteredPoint); + } + + public void update(FrameTuple3DReadOnly unfilteredPoint) + { + checkReferenceFrameMatch(unfilteredPoint); + update((Tuple3DReadOnly) unfilteredPoint); + } + + public void update(Tuple3DReadOnly unfilteredTuple3D) + { + update(unfilteredTuple3D.getX(), unfilteredTuple3D.getY(), unfilteredTuple3D.getZ()); + } + + private final Point3D unfilteredPoint3D = new Point3D(); + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + else + { + unfilteredPoint3D.set(xUnfiltered, yUnfiltered, zUnfiltered); + interpolate(unfilteredPoint3D, this, alpha.getValue()); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePose3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePose3D.java new file mode 100644 index 00000000..8c23d2f9 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePose3D.java @@ -0,0 +1,112 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.geometry.Pose3D; +import us.ihmc.euclid.geometry.interfaces.Pose3DReadOnly; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FramePose3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePose3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFramePose3D extends YoFramePose3D implements ProcessingYoVariable +{ + private final DoubleProvider alpha; + private final FramePose3DReadOnly unfilteredPose; + private final YoBoolean hasBeenCalled; + + private final Pose3D poseMeasured = new Pose3D(); + private final Pose3D posePreviousFiltered = new Pose3D(); + + public AlphaFilteredYoFramePose3D(String namePrefix, String nameSuffix, FramePose3DReadOnly unfilteredPose, DoubleProvider alpha, YoRegistry registry) + { + this(namePrefix, nameSuffix, unfilteredPose, alpha, unfilteredPose.getReferenceFrame(), registry); + } + + private AlphaFilteredYoFramePose3D(String namePrefix, + String nameSuffix, + FramePose3DReadOnly unfilteredPose, + DoubleProvider alpha, + ReferenceFrame referenceFrame, + YoRegistry registry) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + this.unfilteredPose = unfilteredPose; + + if (alpha == null) + alpha = VariableTools.createAlphaYoDouble(namePrefix, "", 0.0, registry); + this.alpha = alpha; + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + } + + public AlphaFilteredYoFramePose3D(YoFramePoint3D position, + YoFrameQuaternion orientation, + FramePose3DReadOnly unfilteredPose, + DoubleProvider alpha, + YoRegistry registry) + { + super(position, orientation); + this.unfilteredPose = unfilteredPose; + + String namePrefix = YoGeometryNameTools.getCommonPrefix(position.getNamePrefix(), orientation.getNamePrefix()); + String nameSuffix = YoGeometryNameTools.getCommonSuffix(position.getNameSuffix(), orientation.getNameSuffix()); + + if (alpha == null) + alpha = VariableTools.createAlphaYoDouble(namePrefix, "", 0.0, registry); + this.alpha = alpha; + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + } + + @Override + public void update() + { + if (unfilteredPose == null) + { + throw new NullPointerException( + "AlphaFilteredYoFramePose3D must be constructed with a non null " + "pose variable to call update(), otherwise use update(Pose3DReadOnly)"); + } + + poseMeasured.set(unfilteredPose); + update(poseMeasured); + } + + public void update(FramePose3DReadOnly rawPose) + { + checkReferenceFrameMatch(rawPose); + poseMeasured.set(rawPose); + update(poseMeasured); + } + + public void update(Pose3DReadOnly rawPose) + { + if (hasBeenCalled.getBooleanValue()) + { + posePreviousFiltered.set(this); + + interpolate(poseMeasured, posePreviousFiltered, alpha.getValue()); // qPreviousFiltered 'gets multiplied by alpha' + } + else + { + set(poseMeasured); + hasBeenCalled.set(true); + } + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + public FramePose3DReadOnly getUnfilteredPose() + { + return unfilteredPose; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternion.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternion.java new file mode 100644 index 00000000..00912e2e --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternion.java @@ -0,0 +1,95 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFrameQuaternion extends YoFrameQuaternion implements ProcessingYoVariable +{ + private final YoFrameQuaternion unfilteredQuaternion; + private final DoubleProvider alpha; + private final YoBoolean hasBeenCalled; + private final Quaternion qMeasured = new Quaternion(); + private final Quaternion qPreviousFiltered = new Quaternion(); + private final Quaternion qNewFiltered = new Quaternion(); + + public AlphaFilteredYoFrameQuaternion(String namePrefix, String nameSuffix, YoFrameQuaternion unfilteredQuaternion, DoubleProvider alpha, + YoRegistry registry) + { + this(namePrefix, nameSuffix, unfilteredQuaternion, alpha, unfilteredQuaternion.getReferenceFrame(), registry); + } + + private AlphaFilteredYoFrameQuaternion(String namePrefix, String nameSuffix, YoFrameQuaternion unfilteredQuaternion, DoubleProvider alpha, + ReferenceFrame referenceFrame, YoRegistry registry) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + this.unfilteredQuaternion = unfilteredQuaternion; + + if (alpha == null) + alpha = VariableTools.createAlphaYoDouble(namePrefix, "", 0.0, registry); + this.alpha = alpha; + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + } + + @Override + public void update() + { + if (unfilteredQuaternion == null) + { + throw new NullPointerException("AlphaFilteredYoFrameQuaternion must be constructed with a non null " + + "quaternion variable to call update(), otherwise use update(Quat4d)"); + } + + qMeasured.set(unfilteredQuaternion); + update(qMeasured); + } + + public void update(FrameOrientation3DReadOnly rawOrientation) + { + checkReferenceFrameMatch(rawOrientation); + qMeasured.set(rawOrientation); + update(qMeasured); + } + + public void update(Orientation3DReadOnly rawOrientation) + { + qMeasured.set(rawOrientation); + update(qMeasured); + } + + private void update(QuaternionReadOnly qMeasured) + { + if (hasBeenCalled.getBooleanValue()) + { + qPreviousFiltered.set(this); + + qNewFiltered.interpolate(qMeasured, qPreviousFiltered, alpha.getValue()); // qPreviousFiltered 'gets multiplied by alpha' + set(qNewFiltered); + } + else + { + set(qMeasured); + hasBeenCalled.set(true); + } + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + public YoFrameQuaternion getUnfilteredQuaternion() + { + return unfilteredQuaternion; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2D.java new file mode 100644 index 00000000..690ce308 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2D.java @@ -0,0 +1,90 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple2DReadOnly; +import us.ihmc.euclid.tuple2D.Vector2D; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector2D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFrameVector2D extends YoFrameVector2D +{ + private final DoubleProvider alpha; + + private final FrameTuple2DReadOnly unfilteredPosition; + private final YoBoolean hasBeenCalled; + + public AlphaFilteredYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, double alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createAlphaYoDouble(namePrefix, nameSuffix, alpha, registry), referenceFrame); + } + + public AlphaFilteredYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, alpha, referenceFrame, null); + } + + public AlphaFilteredYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + FrameTuple2DReadOnly unfilteredFrameTuple2D) + { + this(namePrefix, nameSuffix, registry, alpha, unfilteredFrameTuple2D.getReferenceFrame(), unfilteredFrameTuple2D); + } + + private AlphaFilteredYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame, + FrameTuple2DReadOnly unfilteredPosition) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.alpha = alpha; + this.unfilteredPosition = unfilteredPosition; + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (unfilteredPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(unfilteredPosition); + } + + public void update(FrameTuple2DReadOnly unfilteredFrameTuple2D) + { + checkReferenceFrameMatch(unfilteredFrameTuple2D); + update((Tuple2DReadOnly) unfilteredFrameTuple2D); + } + + public void update(Tuple2DReadOnly unfilteredTuple2D) + { + update(unfilteredTuple2D.getX(), unfilteredTuple2D.getY()); + } + + private final Vector2D unfilteredVector2D = new Vector2D(); + + public void update(double xUnfiltered, double yUnfiltered) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered); + } + else + { + unfilteredVector2D.set(xUnfiltered, yUnfiltered); + interpolate(unfilteredVector2D, this, alpha.getValue()); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3D.java new file mode 100644 index 00000000..2304541d --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3D.java @@ -0,0 +1,100 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoFrameVector3D extends YoFrameVector3D implements ProcessingYoVariable +{ + private final DoubleProvider alphaProvider; + + private final FrameTuple3DReadOnly position; + private final YoBoolean hasBeenCalled; + + public AlphaFilteredYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, double alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createAlphaYoDouble(namePrefix, nameSuffix, alpha, registry), referenceFrame); + } + + public AlphaFilteredYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, alpha, referenceFrame, null); + } + + public AlphaFilteredYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, double alpha, + FrameTuple3DReadOnly unfilteredFrameTuple3D) + { + this(namePrefix, nameSuffix, registry, VariableTools.createAlphaYoDouble(namePrefix, nameSuffix, alpha, registry), + unfilteredFrameTuple3D.getReferenceFrame(), unfilteredFrameTuple3D); + } + + public AlphaFilteredYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + FrameTuple3DReadOnly unfilteredFrameTuple3D) + { + this(namePrefix, nameSuffix, registry, alpha, unfilteredFrameTuple3D.getReferenceFrame(), unfilteredFrameTuple3D); + } + + private AlphaFilteredYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, ReferenceFrame referenceFrame, + FrameTuple3DReadOnly unfilteredFrameTuple3D) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + alphaProvider = alpha; + + position = unfilteredFrameTuple3D; + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + @Override + public void update() + { + if (position == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(position); + } + + public void update(FrameTuple3DReadOnly unfilteredFrameTuple3D) + { + checkReferenceFrameMatch(unfilteredFrameTuple3D); + update((Tuple3DReadOnly) unfilteredFrameTuple3D); + } + + public void update(Tuple3DReadOnly unfilteredTuple3D) + { + update(unfilteredTuple3D.getX(), unfilteredTuple3D.getY(), unfilteredTuple3D.getZ()); + } + + private final Vector3D unfilteredVector3D = new Vector3D(); + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + else + { + unfilteredVector3D.set(xUnfiltered, yUnfiltered, zUnfiltered); + interpolate(unfilteredVector3D, this, alphaProvider.getValue()); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoMutableFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoMutableFrameVector3D.java new file mode 100644 index 00000000..b5d6d4f9 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoMutableFrameVector3D.java @@ -0,0 +1,89 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoMutableFrameVector3D; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class AlphaFilteredYoMutableFrameVector3D extends YoMutableFrameVector3D implements ProcessingYoVariable +{ + private final DoubleProvider alpha; + + private final FrameTuple3DReadOnly unfilteredFrameVector; + private final YoBoolean hasBeenCalled; + + public AlphaFilteredYoMutableFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, alpha, referenceFrame, null); + } + + public AlphaFilteredYoMutableFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + FrameTuple3DReadOnly unfilteredFrameVector) + { + this(namePrefix, nameSuffix, registry, alpha, unfilteredFrameVector.getReferenceFrame(), unfilteredFrameVector); + } + + private AlphaFilteredYoMutableFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider alpha, + ReferenceFrame referenceFrame, FrameTuple3DReadOnly unfilteredFrameVector) + { + super(namePrefix, nameSuffix, registry, referenceFrame); + + this.alpha = alpha; + this.unfilteredFrameVector = unfilteredFrameVector; + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + @Override + public void update() + { + if (unfilteredFrameVector == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(unfilteredFrameVector); + } + + public void update(FrameTuple3DReadOnly unfilteredFrameTuple) + { + checkReferenceFrameMatch(unfilteredFrameTuple); + update((Tuple3DReadOnly) unfilteredFrameTuple); + } + + public void update(Tuple3DReadOnly unfilteredTuple) + { + update(unfilteredTuple.getX(), unfilteredTuple.getY(), unfilteredTuple.getZ()); + } + + private final Vector3D unfilteredVector3D = new Vector3D(); + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + else + { + unfilteredVector3D.set(xUnfiltered, yUnfiltered, zUnfiltered); + interpolate(unfilteredVector3D, this, alpha.getValue()); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashCompensatingVelocityYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashCompensatingVelocityYoFrameVector3D.java new file mode 100644 index 00000000..b2d45f92 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashCompensatingVelocityYoFrameVector3D.java @@ -0,0 +1,71 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.BacklashCompensatingVelocityYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; +import us.ihmc.yoVariables.variable.YoDouble; + +public class BacklashCompensatingVelocityYoFrameVector3D extends YoFrameVector3D +{ + private final BacklashCompensatingVelocityYoVariable xDot, yDot, zDot; + + public BacklashCompensatingVelocityYoFrameVector3D(String namePrefix, + String nameSuffix, + YoDouble alpha, + double dt, + YoDouble slopTime, + YoRegistry registry, + YoFramePoint3D yoFramePointToDifferentiate) + { + this(new BacklashCompensatingVelocityYoVariable(YoGeometryNameTools.createXName(namePrefix, nameSuffix), + "", + alpha, + yoFramePointToDifferentiate.getYoX(), + dt, + slopTime, + registry), + new BacklashCompensatingVelocityYoVariable(YoGeometryNameTools.createYName(namePrefix, nameSuffix), + "", + alpha, + yoFramePointToDifferentiate.getYoY(), + dt, + slopTime, + registry), + new BacklashCompensatingVelocityYoVariable(YoGeometryNameTools.createZName(namePrefix, nameSuffix), + "", + alpha, + yoFramePointToDifferentiate.getYoZ(), + dt, + slopTime, + registry), + yoFramePointToDifferentiate); + } + + private BacklashCompensatingVelocityYoFrameVector3D(BacklashCompensatingVelocityYoVariable xDot, + BacklashCompensatingVelocityYoVariable yDot, + BacklashCompensatingVelocityYoVariable zDot, + YoFramePoint3D yoFramePointToDifferentiate) + { + super(xDot, yDot, zDot, yoFramePointToDifferentiate.getReferenceFrame()); + + this.xDot = xDot; + this.yDot = yDot; + this.zDot = zDot; + } + + public void update() + { + xDot.update(); + yDot.update(); + zDot.update(); + } + + public void reset() + { + xDot.reset(); + yDot.reset(); + zDot.reset(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashProcessingYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashProcessingYoFrameVector3D.java new file mode 100644 index 00000000..2d49e969 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/BacklashProcessingYoFrameVector3D.java @@ -0,0 +1,57 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameTuple3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.BacklashProcessingYoVariable; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +/** + * This class is designed to perform an estimate of a velocity signal that may contain backlash. It does essentially the same as + * {@link BacklashCompensatingVelocityYoFrameVector3D}, except it takes a velocity signal as input. + * + * It works by zeroing out the estimated velocity whenever the finite-differenced velocity changes sign. It then ramps this value back up to the value returned + * by finite estimating over the course of the slop duration. It assumes that the slop time is some fixed, constant period when the estimate is unreliable. + */ +public class BacklashProcessingYoFrameVector3D extends YoFrameVector3D implements ProcessingYoVariable +{ + private final BacklashProcessingYoVariable xDot, yDot, zDot; + + public BacklashProcessingYoFrameVector3D(String namePrefix, String nameSuffix, double dt, DoubleProvider slopTime, + YoRegistry registry, YoFrameTuple3D yoFrameTupleToProcess) + { + this(new BacklashProcessingYoVariable(YoGeometryNameTools.createXName(namePrefix, nameSuffix), "", yoFrameTupleToProcess.getYoX(), dt, slopTime, registry), + new BacklashProcessingYoVariable(YoGeometryNameTools.createYName(namePrefix, nameSuffix), "", yoFrameTupleToProcess.getYoY(), dt, slopTime, registry), + new BacklashProcessingYoVariable(YoGeometryNameTools.createZName(namePrefix, nameSuffix), "", yoFrameTupleToProcess.getYoZ(), dt, slopTime, registry), + yoFrameTupleToProcess.getReferenceFrame()); + } + + private BacklashProcessingYoFrameVector3D(BacklashProcessingYoVariable xDot, BacklashProcessingYoVariable yDot, BacklashProcessingYoVariable zDot, + ReferenceFrame referenceFrame) + { + super(xDot, yDot, zDot, referenceFrame); + + this.xDot = xDot; + this.yDot = yDot; + this.zDot = zDot; + } + + @Override + public void update() + { + xDot.update(); + yDot.update(); + zDot.update(); + } + + @Override + public void reset() + { + xDot.reset(); + yDot.reset(); + zDot.reset(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/DeadbandedYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/DeadbandedYoFrameVector3D.java new file mode 100644 index 00000000..4c064393 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/DeadbandedYoFrameVector3D.java @@ -0,0 +1,57 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.DeadbandedYoVariable; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class DeadbandedYoFrameVector3D extends YoFrameVector3D implements ProcessingYoVariable +{ + private final DeadbandedYoVariable x, y, z; + + public DeadbandedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider deadzoneSize, YoFrameVector3D tupleToTrack) + { + this(new DeadbandedYoVariable(YoGeometryNameTools.createXName(namePrefix, nameSuffix), tupleToTrack.getYoX(), deadzoneSize, registry), + new DeadbandedYoVariable(YoGeometryNameTools.createYName(namePrefix, nameSuffix), tupleToTrack.getYoY(), deadzoneSize, registry), + new DeadbandedYoVariable(YoGeometryNameTools.createZName(namePrefix, nameSuffix), tupleToTrack.getYoZ(), deadzoneSize, registry), + tupleToTrack.getReferenceFrame()); + } + + public DeadbandedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider deadzoneSize, ReferenceFrame referenceFrame) + { + this(new DeadbandedYoVariable(YoGeometryNameTools.createXName(namePrefix, nameSuffix), deadzoneSize, registry), + new DeadbandedYoVariable(YoGeometryNameTools.createYName(namePrefix, nameSuffix), deadzoneSize, registry), + new DeadbandedYoVariable(YoGeometryNameTools.createZName(namePrefix, nameSuffix), deadzoneSize, registry), + referenceFrame); + } + + private DeadbandedYoFrameVector3D(DeadbandedYoVariable x, DeadbandedYoVariable y, DeadbandedYoVariable z, ReferenceFrame referenceFrame) + { + super(x, y, z, referenceFrame); + + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void update() + { + x.update(); + y.update(); + z.update(); + } + + public void update(FrameTuple3DReadOnly frameTuple) + { + checkReferenceFrameMatch(frameTuple); + + x.update(frameTuple.getX()); + y.update(frameTuple.getY()); + z.update(frameTuple.getZ()); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector2D.java new file mode 100644 index 00000000..c43f4327 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector2D.java @@ -0,0 +1,113 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple2DReadOnly; +import us.ihmc.euclid.tuple2D.Vector2D; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector2D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +/** + *

+ * {@link FilteredFiniteDifferenceYoFrameVector2D} + *

+ * + *

+ * Differentiates and Filters a {@link YoFrameVector2D} to get its derivative. This derviative is then low pass filtered. + *

+ *
+ *            vel_{n} = alpha * vel{n-1} + (1 - alpha) * (pos_{n} - pos_{n-1})
+ * 
+ */ +public class FilteredFiniteDifferenceYoFrameVector2D extends YoFrameVector2D +{ + private final double dt; + private final DoubleProvider alphaProvider; + + private final YoBoolean hasBeenCalled; + private final FrameTuple2DReadOnly currentPosition; + private final YoFrameVector2D lastPosition; + + public FilteredFiniteDifferenceYoFrameVector2D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + FrameTuple2DReadOnly frameTuple2DToDifferentiate) + { + this(namePrefix, nameSuffix, alpha, dt, registry, frameTuple2DToDifferentiate, frameTuple2DToDifferentiate.getReferenceFrame()); + } + + public FilteredFiniteDifferenceYoFrameVector2D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, alpha, dt, registry, null, referenceFrame); + } + + private FilteredFiniteDifferenceYoFrameVector2D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + FrameTuple2DReadOnly frameTuple2DToDifferentiate, + ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.alphaProvider = alpha; + this.dt = dt; + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + currentPosition = frameTuple2DToDifferentiate; + lastPosition = new YoFrameVector2D(namePrefix + "_lastPosition", nameSuffix, getReferenceFrame(), registry); + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (currentPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(FrameTuple3DReadOnly)"); + } + + update(currentPosition); + } + + public void update(FrameTuple2DReadOnly frameTuple) + { + checkReferenceFrameMatch(frameTuple); + update((Tuple2DReadOnly) frameTuple); + } + + private final Vector2D currentRawDerivative = new Vector2D(); + + public void update(Tuple2DReadOnly currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + lastPosition.set(currentPosition); + setToZero(); + } + + currentRawDerivative.sub(currentPosition, lastPosition); + currentRawDerivative.scale(1.0 / dt); + + interpolate(currentRawDerivative, this, alphaProvider.getValue()); + + lastPosition.set(currentPosition); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector3D.java new file mode 100644 index 00000000..557eff95 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FilteredFiniteDifferenceYoFrameVector3D.java @@ -0,0 +1,113 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +/** + *

+ * {@link FilteredFiniteDifferenceYoFrameVector3D} + *

+ * + *

+ * Differentiates and Filters a {@link YoFrameVector3D} to get its derivative. This derviative is then low pass filtered. + *

+ *
+ *            vel_{n} = alpha * vel{n-1} + (1 - alpha) * (pos_{n} - pos_{n-1})
+ * 
+ */ +public class FilteredFiniteDifferenceYoFrameVector3D extends YoFrameVector3D +{ + private final double dt; + private final DoubleProvider alphaProvider; + + private final YoBoolean hasBeenCalled; + private final FrameTuple3DReadOnly currentPosition; + private final YoFrameVector3D lastPosition; + + public FilteredFiniteDifferenceYoFrameVector3D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + FrameTuple3DReadOnly frameTuple3DToDifferentiate) + { + this(namePrefix, nameSuffix, alpha, dt, registry, frameTuple3DToDifferentiate, frameTuple3DToDifferentiate.getReferenceFrame()); + } + + public FilteredFiniteDifferenceYoFrameVector3D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, alpha, dt, registry, null, referenceFrame); + } + + private FilteredFiniteDifferenceYoFrameVector3D(String namePrefix, + String nameSuffix, + DoubleProvider alpha, + double dt, + YoRegistry registry, + FrameTuple3DReadOnly frameTuple3DToDifferentiate, + ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.alphaProvider = alpha; + this.dt = dt; + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + currentPosition = frameTuple3DToDifferentiate; + lastPosition = new YoFrameVector3D(namePrefix + "_lastPosition", nameSuffix, getReferenceFrame(), registry); + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (currentPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(FrameTuple3DReadOnly)"); + } + + update(currentPosition); + } + + public void update(FrameTuple3DReadOnly frameTuple) + { + checkReferenceFrameMatch(frameTuple); + update((Tuple3DReadOnly) frameTuple); + } + + private final Vector3D currentRawDerivative = new Vector3D(); + + public void update(Tuple3DReadOnly currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + lastPosition.set(currentPosition); + setToZero(); + } + + currentRawDerivative.sub(currentPosition, lastPosition); + currentRawDerivative.scale(1.0 / dt); + + interpolate(currentRawDerivative, this, alphaProvider.getValue()); + + lastPosition.set(currentPosition); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/FiniteDifferenceAngularVelocityYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FiniteDifferenceAngularVelocityYoFrameVector3D.java new file mode 100644 index 00000000..1576414a --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/FiniteDifferenceAngularVelocityYoFrameVector3D.java @@ -0,0 +1,98 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.matrix.RotationMatrix; +import us.ihmc.euclid.matrix.interfaces.RotationMatrixReadOnly; +import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class FiniteDifferenceAngularVelocityYoFrameVector3D extends YoFrameVector3D +{ + private final YoFrameQuaternion orientation; + private final YoFrameQuaternion orientationPreviousValue; + + private final YoBoolean hasBeenCalled; + + private final RotationMatrix currentOrientationMatrix = new RotationMatrix(); + private final RotationMatrix previousOrientationMatrix = new RotationMatrix(); + private final RotationMatrix deltaOrientationMatrix = new RotationMatrix(); + private final AxisAngle deltaAxisAngle = new AxisAngle(); + + private final double dt; + + public FiniteDifferenceAngularVelocityYoFrameVector3D(String namePrefix, ReferenceFrame referenceFrame, double dt, YoRegistry registry) + { + this(namePrefix, null, referenceFrame, dt, registry); + } + + public FiniteDifferenceAngularVelocityYoFrameVector3D(String namePrefix, YoFrameQuaternion orientationToDifferentiate, double dt, YoRegistry registry) + { + this(namePrefix, orientationToDifferentiate, orientationToDifferentiate.getReferenceFrame(), dt, registry); + } + + private FiniteDifferenceAngularVelocityYoFrameVector3D(String namePrefix, YoFrameQuaternion orientationToDifferentiate, ReferenceFrame referenceFrame, double dt, YoRegistry registry) + { + super(namePrefix, referenceFrame, registry); + + this.dt = dt; + + orientation = orientationToDifferentiate; + orientationPreviousValue = new YoFrameQuaternion(namePrefix + "_previous", referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, "", registry); + hasBeenCalled.set(false); + } + + public void update() + { + if (orientation == null) + { + throw new NullPointerException("FiniteDifferenceAngularVelocityYoFrameVector must be constructed with a non null " + + "orientation variable to call update(), otherwise use update(FrameOrientation)"); + } + + currentOrientationMatrix.set(orientation); + update(currentOrientationMatrix); + } + + public void update(FrameOrientation3DReadOnly currentOrientation) + { + checkReferenceFrameMatch(currentOrientation); + + currentOrientationMatrix.set(currentOrientation); + update(currentOrientationMatrix); + } + + public void update(Orientation3DReadOnly currentOrientation) + { + currentOrientationMatrix.set(currentOrientation); + update(currentOrientationMatrix); + } + + public void update(RotationMatrixReadOnly rotationMatrix) + { + if (!hasBeenCalled.getBooleanValue()) + { + orientationPreviousValue.set(rotationMatrix); + hasBeenCalled.set(true); + } + + if (rotationMatrix != currentOrientationMatrix) + currentOrientationMatrix.set(rotationMatrix); + previousOrientationMatrix.set(orientationPreviousValue); + deltaOrientationMatrix.set(currentOrientationMatrix); + deltaOrientationMatrix.multiplyTransposeOther(previousOrientationMatrix); + deltaAxisAngle.set(deltaOrientationMatrix); + + set(deltaAxisAngle.getX(), deltaAxisAngle.getY(), deltaAxisAngle.getZ()); + scale(deltaAxisAngle.getAngle() / dt); + + orientationPreviousValue.set(currentOrientationMatrix); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFramePoint2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFramePoint2D.java new file mode 100644 index 00000000..e06c568e --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFramePoint2D.java @@ -0,0 +1,65 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint2D; +import us.ihmc.yoVariables.filters.MovingAverageYoDouble; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class MovingAverageYoFramePoint2D extends YoFramePoint2D +{ + private final MovingAverageYoDouble x, y; + + public MovingAverageYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, int beta, ReferenceFrame referenceFrame) + { + this(new MovingAverageYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, beta), + new MovingAverageYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, beta), + referenceFrame); + } + + public MovingAverageYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, int beta, YoFramePoint2D unfilteredPoint) + { + this(new MovingAverageYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, beta, unfilteredPoint.getYoX()), + new MovingAverageYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, beta, unfilteredPoint.getYoY()), + unfilteredPoint.getReferenceFrame()); + } + + private MovingAverageYoFramePoint2D(MovingAverageYoDouble x, MovingAverageYoDouble y, ReferenceFrame referenceFrame) + { + super(x, y, referenceFrame); + + this.x = x; + this.y = y; + } + + public void update() + { + x.update(); + y.update(); + } + + public void update(double xUnfiltered, double yUnfiltered) + { + x.update(xUnfiltered); + y.update(yUnfiltered); + } + + public void update(Point2DReadOnly point2dUnfiltered) + { + update(point2dUnfiltered.getX(), point2dUnfiltered.getY()); + } + + public void update(FramePoint2DReadOnly point2dUnfiltered) + { + checkReferenceFrameMatch(point2dUnfiltered); + update((Point2DReadOnly) point2dUnfiltered); + } + + public void reset() + { + x.reset(); + y.reset(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFrameVector2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFrameVector2D.java new file mode 100644 index 00000000..cebccec1 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/MovingAverageYoFrameVector2D.java @@ -0,0 +1,65 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameVector2DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector2D; +import us.ihmc.yoVariables.filters.MovingAverageYoDouble; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class MovingAverageYoFrameVector2D extends YoFrameVector2D +{ + private final MovingAverageYoDouble x, y; + + public MovingAverageYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, int beta, ReferenceFrame referenceFrame) + { + this(new MovingAverageYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, beta), + new MovingAverageYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, beta), + referenceFrame); + } + + public MovingAverageYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, int beta, YoFrameVector2D unfilteredVector) + { + this(new MovingAverageYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, beta, unfilteredVector.getYoX()), + new MovingAverageYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, beta, unfilteredVector.getYoY()), + unfilteredVector.getReferenceFrame()); + } + + private MovingAverageYoFrameVector2D(MovingAverageYoDouble x, MovingAverageYoDouble y, ReferenceFrame referenceFrame) + { + super(x, y, referenceFrame); + + this.x = x; + this.y = y; + } + + public void update() + { + x.update(); + y.update(); + } + + public void update(double xUnfiltered, double yUnfiltered) + { + x.update(xUnfiltered); + y.update(yUnfiltered); + } + + public void update(Vector2DReadOnly vector2dUnfiltered) + { + update(vector2dUnfiltered.getX(), vector2dUnfiltered.getY()); + } + + public void update(FrameVector2DReadOnly vector2dUnfiltered) + { + checkReferenceFrameMatch(vector2dUnfiltered); + update((Vector2DReadOnly) vector2dUnfiltered); + } + + public void reset() + { + x.reset(); + y.reset(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientation.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientation.java new file mode 100644 index 00000000..fef4624a --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientation.java @@ -0,0 +1,145 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameQuaternion; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameYawPitchRoll; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class RateLimitedYoFrameOrientation extends YoFrameYawPitchRoll +{ + private final DoubleProvider maxRateVariable; + + private final YoFrameYawPitchRoll rawOrientation; + private final YoBoolean limited; + private final YoBoolean hasBeenCalled; + private final double dt; + + public RateLimitedYoFrameOrientation(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + YoFrameYawPitchRoll rawOrientation) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawOrientation, rawOrientation.getReferenceFrame()); + } + + public RateLimitedYoFrameOrientation(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, null, referenceFrame); + } + + public RateLimitedYoFrameOrientation(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + YoFrameYawPitchRoll rawOrientation) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawOrientation, + rawOrientation.getReferenceFrame()); + } + + public RateLimitedYoFrameOrientation(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, null, referenceFrame); + } + + private RateLimitedYoFrameOrientation(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + YoFrameYawPitchRoll rawOrientation, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawOrientation = rawOrientation; + + this.dt = dt; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawOrientation == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawOrientation); + } + + public void update(YoFrameYawPitchRoll yoFrameVectorUnfiltered) + { + checkReferenceFrameMatch(yoFrameVectorUnfiltered); + update(yoFrameVectorUnfiltered.getYaw(), yoFrameVectorUnfiltered.getPitch(), yoFrameVectorUnfiltered.getRoll()); + } + + public void update(FrameQuaternion frameOrientationUnfiltered) + { + checkReferenceFrameMatch(frameOrientationUnfiltered); + update((QuaternionReadOnly) frameOrientationUnfiltered); + } + + private final Quaternion quaternionUnfiltered = new Quaternion(); + + public void update(double yawUnfiltered, double pitchUnfiltered, double rollUnfiltered) + { + quaternionUnfiltered.setYawPitchRoll(yawUnfiltered, pitchUnfiltered, rollUnfiltered); + update(quaternionUnfiltered); + } + + private final Quaternion quaternionFiltered = new Quaternion(); + private final Quaternion difference = new Quaternion(); + private final Vector3D limitedRotationVector = new Vector3D(); + + public void update(QuaternionReadOnly quaternionUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + limited.set(false); + set(quaternionUnfiltered); + return; + } + + quaternionFiltered.set(this); + + if (quaternionFiltered.dot(quaternionUnfiltered) > 0.0) + { + difference.difference(quaternionFiltered, quaternionUnfiltered); + } + else + { + difference.setAndNegate(quaternionUnfiltered); + difference.preMultiplyConjugateOther(quaternionFiltered); + } + + difference.getRotationVector(limitedRotationVector); + boolean clipped = limitedRotationVector.clipToMaxNorm(dt * maxRateVariable.getValue()); + limited.set(clipped); + + if (clipped) + { + difference.setRotationVector(limitedRotationVector); + quaternionFiltered.multiply(difference); + set(quaternionFiltered); + } + else + { + set(quaternionUnfiltered); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint2D.java new file mode 100644 index 00000000..0a5575d1 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint2D.java @@ -0,0 +1,135 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector2D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple2DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint2D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +public class RateLimitedYoFramePoint2D extends YoFramePoint2D +{ + private final DoubleProvider maxRateVariable; + + private final FrameTuple2DReadOnly rawPosition; + private final YoBoolean limited; + private final YoBoolean hasBeenCalled; + private final double dt; + + private final FrameVector2D differenceVector = new FrameVector2D(); + + private static DoubleProvider createMaxRateYoDouble(String namePrefix, String nameSuffix, double initialValue, YoRegistry registry) + { + YoDouble maxRate = new YoDouble(namePrefix + "MaxRate" + nameSuffix, registry); + maxRate.set(initialValue); + return maxRate; + } + + public RateLimitedYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple2DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawPosition, rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, null, referenceFrame); + } + + public RateLimitedYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + FrameTuple2DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawPosition, + rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, null, referenceFrame); + } + + private RateLimitedYoFramePoint2D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple2DReadOnly rawPosition, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawPosition = rawPosition; + + this.dt = dt; + + reset(); + } + + public void setAndUpdate(FramePoint2DReadOnly framePoint2D) + { + super.set(framePoint2D); + hasBeenCalled.set(true); + } + + public void setAndUpdate(FramePoint3DReadOnly framePoint3D) + { + super.set(framePoint3D); + hasBeenCalled.set(true); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawPosition); + } + + public void update(FrameTuple2DReadOnly frameVectorUnfiltered) + { + checkReferenceFrameMatch(frameVectorUnfiltered); + update(frameVectorUnfiltered.getX(), frameVectorUnfiltered.getY()); + } + + public void update(Tuple2DReadOnly vectorUnfiltered) + { + update(vectorUnfiltered.getX(), vectorUnfiltered.getY()); + } + + public void update(double xUnfiltered, double yUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered); + } + + if (maxRateVariable.getValue() < 0) + throw new RuntimeException("The maxRate parameter in the " + getClass().getSimpleName() + " cannot be negative."); + + differenceVector.setToZero(getReferenceFrame()); + differenceVector.set(xUnfiltered, yUnfiltered); + differenceVector.sub(getX(), getY()); + + limited.set(differenceVector.clipToMaxNorm(maxRateVariable.getValue() * dt)); + add(differenceVector); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint3D.java new file mode 100644 index 00000000..5b7ad253 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePoint3D.java @@ -0,0 +1,113 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class RateLimitedYoFramePoint3D extends YoFramePoint3D +{ + private final DoubleProvider maxRateVariable; + + private final FrameTuple3DReadOnly rawPosition; + private final YoBoolean limited; + private final YoBoolean hasBeenCalled; + private final double dt; + + private final FrameVector3D differenceVector = new FrameVector3D(); + + public RateLimitedYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawPosition, rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, null, referenceFrame); + } + + public RateLimitedYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawPosition, + rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, null, referenceFrame); + } + + private RateLimitedYoFramePoint3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawPosition = rawPosition; + + this.dt = dt; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawPosition); + } + + public void update(FrameTuple3DReadOnly frameVectorUnfiltered) + { + checkReferenceFrameMatch(frameVectorUnfiltered); + update(frameVectorUnfiltered.getX(), frameVectorUnfiltered.getY(), frameVectorUnfiltered.getZ()); + } + + public void update(Tuple3DReadOnly vectorUnfiltered) + { + update(vectorUnfiltered.getX(), vectorUnfiltered.getY(), vectorUnfiltered.getZ()); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + + if (maxRateVariable.getValue() < 0) + throw new RuntimeException("The maxRate parameter in the " + getClass().getSimpleName() + " cannot be negative."); + + differenceVector.setToZero(getReferenceFrame()); + differenceVector.set(xUnfiltered, yUnfiltered, zUnfiltered); + differenceVector.sub(getX(), getY(), getZ()); + + limited.set(differenceVector.clipToMaxLength(maxRateVariable.getValue() * dt)); + add(differenceVector); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePose3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePose3D.java new file mode 100644 index 00000000..f27cdf31 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFramePose3D.java @@ -0,0 +1,103 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.geometry.tools.EuclidGeometryIOTools; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePoint3DBasics; +import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePose3DBasics; +import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameQuaternionBasics; +import us.ihmc.euclid.referenceFrame.interfaces.FramePose3DReadOnly; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class RateLimitedYoFramePose3D implements FixedFramePose3DBasics +{ + private final RateLimitedYoFramePoint3D position; + private final RateLimitedYoFrameQuaternion orientation; + + public RateLimitedYoFramePose3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FramePose3DReadOnly rawPose) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawPose, rawPose.getReferenceFrame()); + } + + public RateLimitedYoFramePose3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, null, referenceFrame); + } + + public RateLimitedYoFramePose3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, FramePose3DReadOnly rawPose) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawPose, rawPose.getReferenceFrame()); + } + + public RateLimitedYoFramePose3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, null, referenceFrame); + } + + private RateLimitedYoFramePose3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FramePose3DReadOnly rawPose, ReferenceFrame referenceFrame) + { + if (rawPose != null) + { + this.position = new RateLimitedYoFramePoint3D(namePrefix, "Position" + nameSuffix, registry, maxRate, dt, rawPose.getPosition()); + this.orientation = new RateLimitedYoFrameQuaternion(namePrefix, "Orientation" + nameSuffix, registry, maxRate, dt, rawPose.getOrientation()); + } + else + { + this.position = new RateLimitedYoFramePoint3D(namePrefix, "Position" + nameSuffix, registry, maxRate, dt, referenceFrame); + this.orientation = new RateLimitedYoFrameQuaternion(namePrefix, "Orientation" + nameSuffix, registry, maxRate, dt, referenceFrame); + } + + reset(); + } + + public void reset() + { + position.reset(); + orientation.reset(); + } + + public void update() + { + position.update(); + orientation.update(); + + set(position, orientation); + } + + public void update(FramePose3DReadOnly framePoseUnfiltered) + { + checkReferenceFrameMatch(framePoseUnfiltered); + position.update(framePoseUnfiltered.getPosition()); + orientation.update(framePoseUnfiltered.getOrientation()); + + set(position, orientation); + } + + @Override + public FixedFramePoint3DBasics getPosition() + { + return position; + } + + @Override + public FixedFrameQuaternionBasics getOrientation() + { + return orientation; + } + + @Override + public ReferenceFrame getReferenceFrame() + { + return position.getReferenceFrame(); + } + + @Override + public String toString() + { + return EuclidGeometryIOTools.getPose3DString(this) + "-" + getReferenceFrame(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternion.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternion.java new file mode 100644 index 00000000..7cbcebea --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternion.java @@ -0,0 +1,144 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FrameQuaternionReadOnly; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class RateLimitedYoFrameQuaternion extends YoFrameQuaternion +{ + private final QuaternionReadOnly rawQuaternion; + private final YoBoolean hasBeenCalled; + private final double dt; + + private final YoBoolean limited; + private final DoubleProvider maxRateVariable; + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + FrameQuaternionReadOnly rawQuaternion) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawQuaternion.getReferenceFrame(), + rawQuaternion); + } + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, referenceFrame, null); + } + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + ReferenceFrame referenceFrame, QuaternionReadOnly rawQuaternion) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, referenceFrame, rawQuaternion); + } + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameQuaternionReadOnly rawQuaternion) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawQuaternion.getReferenceFrame(), rawQuaternion); + } + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, referenceFrame, null); + } + + public RateLimitedYoFrameQuaternion(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame, QuaternionReadOnly rawQuaternion) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawQuaternion = rawQuaternion; + + this.dt = dt; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawQuaternion == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawQuaternion); + } + + private final Quaternion quaternion = new Quaternion(); + + public void update(FrameOrientation3DReadOnly frameOrientationUnfiltered) + { + checkReferenceFrameMatch(frameOrientationUnfiltered); + quaternion.set(frameOrientationUnfiltered); + update(quaternion); + } + + public void update(FrameQuaternionReadOnly frameQuaternionUnfiltered) + { + checkReferenceFrameMatch(frameQuaternionUnfiltered); + quaternion.set(frameQuaternionUnfiltered); + update(quaternion); + } + + private final Quaternion difference = new Quaternion(); + private final Vector3D limitedRotationVector = new Vector3D(); + + public void update(QuaternionReadOnly quaternionUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + limited.set(false); + set(quaternionUnfiltered); + return; + } + + if (dot(quaternionUnfiltered) > 0.0) + { + difference.difference(this, quaternionUnfiltered); + } + else + { + difference.setAndNegate(quaternionUnfiltered); + difference.preMultiplyConjugateOther(this); + } + + difference.getRotationVector(limitedRotationVector); + boolean clipped = limitedRotationVector.clipToMaxNorm(dt * maxRateVariable.getValue()); + limited.set(clipped); + + if (clipped) + { + difference.setRotationVector(limitedRotationVector); + multiply(difference); + } + else + { + set(quaternionUnfiltered); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector2D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector2D.java new file mode 100644 index 00000000..da92c1d8 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector2D.java @@ -0,0 +1,48 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector2D; +import us.ihmc.yoVariables.filters.RateLimitedYoVariable; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class RateLimitedYoFrameVector2D extends YoFrameVector2D +{ + private final RateLimitedYoVariable x, y; + + public RateLimitedYoFrameVector2D(String namePrefix, YoRegistry registry, + DoubleProvider maxRate, double dt, YoFrameVector2D unfilteredVector) + { + this(namePrefix, "", registry, maxRate, dt, unfilteredVector); + } + + public RateLimitedYoFrameVector2D(String namePrefix, String nameSuffix, YoRegistry registry, + DoubleProvider maxRate, double dt, YoFrameVector2D unfilteredVector) + { + this(new RateLimitedYoVariable(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, maxRate, unfilteredVector.getYoX(), dt), + new RateLimitedYoVariable(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, maxRate, unfilteredVector.getYoY(), dt), + unfilteredVector.getReferenceFrame()); + } + + private RateLimitedYoFrameVector2D(RateLimitedYoVariable x, RateLimitedYoVariable y, ReferenceFrame referenceFrame) + { + super(x, y, referenceFrame); + + this.x = x; + this.y = y; + } + + public void update() + { + x.update(); + y.update(); + } + + public void reset() + { + x.reset(); + y.reset(); + } +} + diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector3D.java new file mode 100644 index 00000000..64760b2e --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameVector3D.java @@ -0,0 +1,114 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class RateLimitedYoFrameVector3D extends YoFrameVector3D +{ + private final DoubleProvider maxRateVariable; + + private final FrameTuple3DReadOnly rawPosition; + private final YoBoolean limited; + private final YoBoolean hasBeenCalled; + private final double dt; + + private final FrameVector3D differenceVector = new FrameVector3D(); + + + public RateLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawPosition, rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, null, referenceFrame); + } + + public RateLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, + FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, rawPosition, + rawPosition.getReferenceFrame()); + } + + public RateLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, double maxRate, double dt, ReferenceFrame referenceFrame) + { + this(namePrefix, nameSuffix, registry, VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, maxRate, registry), dt, null, referenceFrame); + } + + private RateLimitedYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawPosition = rawPosition; + + this.dt = dt; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawPosition); + } + + public void update(FrameTuple3DReadOnly frameVectorUnfiltered) + { + checkReferenceFrameMatch(frameVectorUnfiltered); + update(frameVectorUnfiltered.getX(), frameVectorUnfiltered.getY(), frameVectorUnfiltered.getZ()); + } + + public void update(Tuple3DReadOnly vectorUnfiltered) + { + update(vectorUnfiltered.getX(), vectorUnfiltered.getY(), vectorUnfiltered.getZ()); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + + if (maxRateVariable.getValue() < 0) + throw new RuntimeException("The maxRate parameter in the " + getClass().getSimpleName() + " cannot be negative."); + + differenceVector.setToZero(getReferenceFrame()); + differenceVector.set(xUnfiltered, yUnfiltered, zUnfiltered); + differenceVector.sub(getX(), getY(), getZ()); + + limited.set(differenceVector.clipToMaxNorm(maxRateVariable.getValue() * dt)); + add(differenceVector); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoMutableFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoMutableFrameVector3D.java new file mode 100644 index 00000000..8e68b73a --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoMutableFrameVector3D.java @@ -0,0 +1,95 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoMutableFrameVector3D; +import us.ihmc.yoVariables.filters.VariableTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +public class RateLimitedYoMutableFrameVector3D extends YoMutableFrameVector3D +{ + private final DoubleProvider maxRateVariable; + + private final FrameTuple3DReadOnly rawPosition; + private final YoBoolean limited; + private final YoBoolean hasBeenCalled; + private final double dt; + + private final FrameVector3D differenceVector = new FrameVector3D(); + + public RateLimitedYoMutableFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition) + { + this(namePrefix, nameSuffix, registry, maxRate, dt, rawPosition, rawPosition.getReferenceFrame()); + } + + private RateLimitedYoMutableFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, DoubleProvider maxRate, double dt, + FrameTuple3DReadOnly rawPosition, ReferenceFrame referenceFrame) + { + super(namePrefix, nameSuffix, registry, referenceFrame); + + hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(namePrefix, nameSuffix, registry); + limited = VariableTools.createLimitedCalledYoBoolean(namePrefix, nameSuffix, registry); + + if (maxRate == null) + maxRate = VariableTools.createMaxRateYoDouble(namePrefix, nameSuffix, Double.POSITIVE_INFINITY, registry); + + maxRateVariable = maxRate; + + this.rawPosition = rawPosition; + + this.dt = dt; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (rawPosition == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(rawPosition); + } + + public void update(FrameTuple3DReadOnly frameVectorUnfiltered) + { + checkReferenceFrameMatch(frameVectorUnfiltered); + update(frameVectorUnfiltered.getX(), frameVectorUnfiltered.getY(), frameVectorUnfiltered.getZ()); + } + + public void update(Tuple3DReadOnly vectorUnfiltered) + { + update(vectorUnfiltered.getX(), vectorUnfiltered.getY(), vectorUnfiltered.getZ()); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + if (!hasBeenCalled.getBooleanValue() || containsNaN()) + { + hasBeenCalled.set(true); + set(xUnfiltered, yUnfiltered, zUnfiltered); + } + + if (maxRateVariable.getValue() < 0) + throw new RuntimeException("The maxRate parameter in the " + getClass().getSimpleName() + " cannot be negative."); + + differenceVector.setToZero(getReferenceFrame()); + differenceVector.set(xUnfiltered, yUnfiltered, zUnfiltered); + differenceVector.sub(getX(), getY(), getZ()); + + limited.set(differenceVector.clipToMaxLength(maxRateVariable.getValue() * dt)); + add(differenceVector); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3D.java new file mode 100644 index 00000000..556f6bd5 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3D.java @@ -0,0 +1,78 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; +import us.ihmc.yoVariables.variable.YoInteger; + +public class RunningAverageYoFrameVector3D extends YoFrameVector3D +{ + private final YoInteger sampleSize; + private final Tuple3DReadOnly dataSource; + + public RunningAverageYoFrameVector3D(String namePrefix, ReferenceFrame referenceFrame, YoRegistry registry) + { + this(namePrefix, "", registry, referenceFrame, null); + } + + public RunningAverageYoFrameVector3D(String namePrefix, String nameSuffix, ReferenceFrame referenceFrame, YoRegistry registry) + { + this(namePrefix, nameSuffix, registry, referenceFrame, null); + } + + public RunningAverageYoFrameVector3D(String namePrefix, YoRegistry registry, FrameTuple3DReadOnly dataSource) + { + this(namePrefix, "", registry, dataSource.getReferenceFrame(), dataSource); + } + + public RunningAverageYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, FrameTuple3DReadOnly dataSource) + { + this(namePrefix, nameSuffix, registry, dataSource.getReferenceFrame(), dataSource); + } + + private RunningAverageYoFrameVector3D(String namePrefix, String nameSuffix, YoRegistry registry, ReferenceFrame referenceFrame, Tuple3DReadOnly dataSource) + { + super(namePrefix, nameSuffix, referenceFrame, registry); + + this.dataSource = dataSource; + + sampleSize = new YoInteger(YoGeometryNameTools.assembleName(namePrefix, "sampleSize", nameSuffix), registry); + } + + public void update() + { + update(dataSource); + } + + public void update(Tuple3DReadOnly vectorSource) + { + update(vectorSource.getX(), vectorSource.getY(), vectorSource.getZ()); + } + + public void update(FrameTuple3DReadOnly vectorSource) + { + checkReferenceFrameMatch(vectorSource); + update((Tuple3DReadOnly) vectorSource); + } + + public void update(double xSource, double ySource, double zSource) + { + sampleSize.increment(); + addX((xSource - getX()) / sampleSize.getValue()); + addY((ySource - getY()) / sampleSize.getValue()); + addZ((zSource - getZ()) / sampleSize.getValue()); + } + + public void reset() + { + sampleSize.set(0); + } + + public int getSampleSize() + { + return sampleSize.getValue(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/SecondOrderFilteredYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/SecondOrderFilteredYoFrameVector3D.java new file mode 100644 index 00000000..51ae84ab --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/SecondOrderFilteredYoFrameVector3D.java @@ -0,0 +1,128 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.SecondOrderFilteredYoDouble; +import us.ihmc.yoVariables.filters.SecondOrderFilteredYoVariableParameters; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class SecondOrderFilteredYoFrameVector3D extends YoFrameVector3D implements ProcessingYoVariable +{ + private final SecondOrderFilteredYoDouble x, y, z; + + public SecondOrderFilteredYoFrameVector3D(String namePrefix, + String nameSuffix, + YoRegistry registry, + double dt, + SecondOrderFilteredYoVariableParameters parameters, + YoFrameVector3D unfilteredVector) + { + this(new SecondOrderFilteredYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, dt, parameters, unfilteredVector.getYoX()), + new SecondOrderFilteredYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, dt, parameters, unfilteredVector.getYoY()), + new SecondOrderFilteredYoDouble(YoGeometryNameTools.createZName(namePrefix, nameSuffix), registry, dt, parameters, unfilteredVector.getYoZ()), + unfilteredVector.getReferenceFrame()); + } + + private SecondOrderFilteredYoFrameVector3D(SecondOrderFilteredYoDouble x, + SecondOrderFilteredYoDouble y, + SecondOrderFilteredYoDouble z, + ReferenceFrame referenceFrame) + { + super(x, y, z, referenceFrame); + + this.x = x; + this.y = y; + this.z = z; + } + + public static SecondOrderFilteredYoFrameVector3D createSecondOrderFilteredYoFrameVector(String namePrefix, + String nameSuffix, + YoRegistry registry, + double dt, + double naturalFrequencyInHz, + double dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType filterType, + ReferenceFrame referenceFrame) + + { + SecondOrderFilteredYoVariableParameters parameters = new SecondOrderFilteredYoVariableParameters(namePrefix + nameSuffix, + registry, + naturalFrequencyInHz, + dampingRatio, + filterType); + return createSecondOrderFilteredYoFrameVector(namePrefix, nameSuffix, registry, dt, parameters, referenceFrame); + } + + public static SecondOrderFilteredYoFrameVector3D createSecondOrderFilteredYoFrameVector(String namePrefix, + String nameSuffix, + YoRegistry registry, + double dt, + SecondOrderFilteredYoVariableParameters parameters, + ReferenceFrame referenceFrame) + { + SecondOrderFilteredYoDouble x, y, z; + x = new SecondOrderFilteredYoDouble(YoGeometryNameTools.createXName(namePrefix, nameSuffix), registry, dt, parameters); + y = new SecondOrderFilteredYoDouble(YoGeometryNameTools.createYName(namePrefix, nameSuffix), registry, dt, parameters); + z = new SecondOrderFilteredYoDouble(YoGeometryNameTools.createZName(namePrefix, nameSuffix), registry, dt, parameters); + return new SecondOrderFilteredYoFrameVector3D(x, y, z, referenceFrame); + } + + public static SecondOrderFilteredYoFrameVector3D createSecondOrderFilteredYoFrameVector(String namePrefix, + String nameSuffix, + YoRegistry registry, + double dt, + double naturalFrequencyInHz, + double dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType filterType, + YoFrameVector3D unfilteredVector) + { + SecondOrderFilteredYoVariableParameters parameters = new SecondOrderFilteredYoVariableParameters(namePrefix + nameSuffix, + registry, + naturalFrequencyInHz, + dampingRatio, + filterType); + return new SecondOrderFilteredYoFrameVector3D(namePrefix, nameSuffix, registry, dt, parameters, unfilteredVector); + } + + @Override + public void update() + { + x.update(); + y.update(); + z.update(); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + x.update(xUnfiltered); + y.update(yUnfiltered); + z.update(zUnfiltered); + } + + public void update(Vector3D vectorUnfiltered) + { + x.update(vectorUnfiltered.getX()); + y.update(vectorUnfiltered.getY()); + z.update(vectorUnfiltered.getZ()); + } + + public void update(FrameVector3D vectorUnfiltered) + { + checkReferenceFrameMatch(vectorUnfiltered); + x.update(vectorUnfiltered.getX()); + y.update(vectorUnfiltered.getY()); + z.update(vectorUnfiltered.getZ()); + } + + @Override + public void reset() + { + x.reset(); + y.reset(); + z.reset(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/euclid/filters/SimpleMovingAverageFilteredYoFrameVector3D.java b/src/filters/java/us/ihmc/yoVariables/euclid/filters/SimpleMovingAverageFilteredYoFrameVector3D.java new file mode 100644 index 00000000..07e32c51 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/euclid/filters/SimpleMovingAverageFilteredYoFrameVector3D.java @@ -0,0 +1,96 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameVector3D; +import us.ihmc.yoVariables.filters.ProcessingYoVariable; +import us.ihmc.yoVariables.filters.SimpleMovingAverageFilteredYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.tools.YoGeometryNameTools; + +public class SimpleMovingAverageFilteredYoFrameVector3D extends YoFrameVector3D implements ProcessingYoVariable +{ + private final SimpleMovingAverageFilteredYoVariable x, y, z; + + private SimpleMovingAverageFilteredYoFrameVector3D(SimpleMovingAverageFilteredYoVariable x, SimpleMovingAverageFilteredYoVariable y, + SimpleMovingAverageFilteredYoVariable z, ReferenceFrame referenceFrame) + { + super(x, y, z, referenceFrame); + + this.x = x; + this.y = y; + this.z = z; + } + + public static SimpleMovingAverageFilteredYoFrameVector3D createSimpleMovingAverageFilteredYoFrameVector(String namePrefix, String nameSuffix, int windowSize, + ReferenceFrame referenceFrame, YoRegistry registry) + { + String xName = YoGeometryNameTools.createXName(namePrefix, nameSuffix); + String yName = YoGeometryNameTools.createYName(namePrefix, nameSuffix); + String zName = YoGeometryNameTools.createZName(namePrefix, nameSuffix); + + SimpleMovingAverageFilteredYoVariable x = new SimpleMovingAverageFilteredYoVariable(xName, windowSize, registry); + SimpleMovingAverageFilteredYoVariable y = new SimpleMovingAverageFilteredYoVariable(yName, windowSize, registry); + SimpleMovingAverageFilteredYoVariable z = new SimpleMovingAverageFilteredYoVariable(zName, windowSize, registry); + + return new SimpleMovingAverageFilteredYoFrameVector3D(x, y, z, referenceFrame); + } + + public static SimpleMovingAverageFilteredYoFrameVector3D createSimpleMovingAverageFilteredYoFrameVector(String namePrefix, String nameSuffix, int windowSize, + YoFrameVector3D unfilteredVector, YoRegistry registry) + { + String xName = YoGeometryNameTools.createXName(namePrefix, nameSuffix); + String yName = YoGeometryNameTools.createYName(namePrefix, nameSuffix); + String zName = YoGeometryNameTools.createZName(namePrefix, nameSuffix); + + SimpleMovingAverageFilteredYoVariable x = new SimpleMovingAverageFilteredYoVariable(xName, windowSize, unfilteredVector.getYoX(), registry); + SimpleMovingAverageFilteredYoVariable y = new SimpleMovingAverageFilteredYoVariable(yName, windowSize, unfilteredVector.getYoY(), registry); + SimpleMovingAverageFilteredYoVariable z = new SimpleMovingAverageFilteredYoVariable(zName, windowSize, unfilteredVector.getYoZ(), registry); + + return new SimpleMovingAverageFilteredYoFrameVector3D(x, y, z, unfilteredVector.getReferenceFrame()); + } + + @Override + public void update() + { + x.update(); + y.update(); + z.update(); + } + + public void update(double xUnfiltered, double yUnfiltered, double zUnfiltered) + { + x.update(xUnfiltered); + y.update(yUnfiltered); + z.update(zUnfiltered); + } + + public void update(Vector3D vectorUnfiltered) + { + x.update(vectorUnfiltered.getX()); + y.update(vectorUnfiltered.getY()); + z.update(vectorUnfiltered.getZ()); + } + + public void update(FrameVector3D vectorUnfiltered) + { + checkReferenceFrameMatch(vectorUnfiltered); + x.update(vectorUnfiltered.getX()); + y.update(vectorUnfiltered.getY()); + z.update(vectorUnfiltered.getZ()); + } + + @Override + public void reset() + { + x.reset(); + y.reset(); + z.reset(); + } + + public boolean getHasBufferWindowFilled() + { + return x.getHasBufferWindowFilled() && y.getHasBufferWindowFilled() && z.getHasBufferWindowFilled(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AccelerationLimitedYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/AccelerationLimitedYoVariable.java new file mode 100644 index 00000000..ee5817bc --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AccelerationLimitedYoVariable.java @@ -0,0 +1,141 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * Creates a yo variable + */ +public class AccelerationLimitedYoVariable extends YoDouble +{ + private final double dt; + + private final YoBoolean hasBeenInitialized; + + private final YoDouble smoothedRate; + private final YoDouble smoothedAcceleration; + + private final YoDouble positionGain; + private final YoDouble velocityGain; + + private DoubleProvider maximumRate; + private DoubleProvider maximumAcceleration; + + private final DoubleProvider inputVariable; + + public AccelerationLimitedYoVariable(String name, YoRegistry registry, DoubleProvider maxRate, DoubleProvider maxAcceleration, double dt) + { + this(name, registry, maxRate, maxAcceleration, null, dt); + } + + public AccelerationLimitedYoVariable(String name, YoRegistry registry, DoubleProvider maxRate, DoubleProvider maxAcceleration, + DoubleProvider inputVariable, double dt) + { + super(name, registry); + + if (maxRate != null && maxAcceleration != null) + { + this.maximumRate = maxRate; + this.maximumAcceleration = maxAcceleration; + } + + this.dt = dt; + + hasBeenInitialized = new YoBoolean(name + "HasBeenInitialized", registry); + + smoothedRate = new YoDouble(name + "SmoothedRate", registry); + smoothedAcceleration = new YoDouble(name + "SmoothedAcceleration", registry); + + positionGain = new YoDouble(name + "PositionGain", registry); + velocityGain = new YoDouble(name + "VelocityGain", registry); + + double w0 = 2.0 * Math.PI * 16.0; + double zeta = 1.0; + + setGainsByPolePlacement(w0, zeta); + hasBeenInitialized.set(false); + + this.inputVariable = inputVariable; + } + + + public void setGainsByPolePlacement(double w0, double zeta) + { + positionGain.set(w0 * w0); + velocityGain.set(2.0 * zeta * w0); + } + + public YoDouble getPositionGain() + { + return positionGain; + } + + public YoDouble getVelocityGain() + { + return velocityGain; + } + + public void update() + { + update(inputVariable.getValue()); + } + + public void update(double input) + { + if (!hasBeenInitialized.getBooleanValue()) + initialize(input); + + double positionError = input - this.getDoubleValue(); + double acceleration = -velocityGain.getDoubleValue() * smoothedRate.getDoubleValue() + positionGain.getDoubleValue() * positionError; + acceleration = MathTools.clamp(acceleration, -maximumAcceleration.getValue(), maximumAcceleration.getValue()); + + smoothedAcceleration.set(acceleration); + smoothedRate.add(smoothedAcceleration.getDoubleValue() * dt); + smoothedRate.set(MathTools.clamp(smoothedRate.getDoubleValue(), maximumRate.getValue())); + this.add(smoothedRate.getDoubleValue() * dt); + } + + public void initialize(double input) + { + this.set(input); + smoothedRate.set(0.0); + smoothedAcceleration.set(0.0); + + this.hasBeenInitialized.set(true); + } + + public void reset() + { + this.hasBeenInitialized.set(false); + smoothedRate.set(0.0); + smoothedAcceleration.set(0.0); + } + + public YoDouble getSmoothedRate() + { + return smoothedRate; + } + + public YoDouble getSmoothedAcceleration() + { + return smoothedAcceleration; + } + + public boolean hasBeenInitialized() + { + return hasBeenInitialized.getBooleanValue(); + } + + public double getMaximumRate() + { + return maximumRate.getValue(); + } + + public double getMaximumAcceleration() + { + return maximumAcceleration.getValue(); + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaBasedOnBreakFrequencyProvider.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaBasedOnBreakFrequencyProvider.java new file mode 100644 index 00000000..836eeb91 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaBasedOnBreakFrequencyProvider.java @@ -0,0 +1,55 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; + +/** + * This class calculates the alpha value given a break frequency provider + * + * The value gets cached to avoid unnecessary calculations + * + * @author Jesper Smith + * + */ +public class AlphaBasedOnBreakFrequencyProvider implements DoubleProvider +{ + + private final DoubleProvider breakFrequencyProvider; + private final double dt; + + private double previousBreakFrequency = Double.NaN; + private double alpha = 0.0; + + /** + * Create a new provider using the break frequency provided by double provider + * + * @param breakFrequencyProvider + * @param dt + */ + public AlphaBasedOnBreakFrequencyProvider(DoubleProvider breakFrequencyProvider, double dt) + { + this.breakFrequencyProvider = breakFrequencyProvider; + this.dt = dt; + } + + /** + * Get the desired alpha value, based on the break frequency given by the breakFrequencyProvider + * + * The value gets cached, and checked based on the current value of the breakFrequencyProvider. + * This will be safe to rewind. + * + * @return alpha variable based on the value of breakFrequencyProvider and dt + */ + @Override + public double getValue() + { + double currentBreakFrequency = breakFrequencyProvider.getValue(); + if (currentBreakFrequency != previousBreakFrequency) + { + alpha = AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(currentBreakFrequency, dt); + previousBreakFrequency = currentBreakFrequency; + } + + return alpha; + } + +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariable.java new file mode 100644 index 00000000..6329dbe1 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariable.java @@ -0,0 +1,96 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + *

+ * An {@code AlphaBetaFilteredYoVariable} is a filtered version of {@link YoDouble}, and estimates the velocity of some value it is tracking. + * Either a {@link YoDouble} holding the position value is passed in to the + * constructor and {@link #update()} is called every tick, or {@link #update(double)} is + * called every tick. The {@code AlphaBetaFilteredYoVariable} updates its value + * with the current filtered velocity using + *

+ * xp = x + (dt) v // angular position prediction + * x+ = xp + alpha (xmeas - xp) // adjusted angular position estimate + * v+ = v + beta (xmeas - xp) // adjusted velocity estimate + * + *

+ *
+ *
+ *         For complete reference see:
+ *                http://www.mstarlabs.com/control/engspeed.html#Ref2
+ *
+ *         
+ */ +public class AlphaBetaFilteredYoVariable extends YoDouble +{ + private double alpha = 0.0, beta = 0.0; + + private final double DT; + private YoDouble alphaVariable = null; + private YoDouble betaVariable = null; + + private final YoDouble positionState; + private final YoDouble xMeasuredVariable; + + public AlphaBetaFilteredYoVariable(String name, + YoRegistry registry, + double alpha, + double beta, + YoDouble positionVariable, + YoDouble xMeasuredVariable, + double DT) + { + super(name, registry); + this.alpha = alpha; + this.beta = beta; + this.DT = DT; + this.positionState = positionVariable; + this.xMeasuredVariable = xMeasuredVariable; + + reset(); + } + + public void reset() + { + } + + public YoDouble getPositionEstimation() + { + return positionState; + } + + public YoDouble getVelocityEstimation() + { + return this; + } + + public void update() + { + update(positionState.getDoubleValue()); + } + + public void update(double position) + { + if (alphaVariable != null) + { + alpha = alphaVariable.getDoubleValue(); + } + + if (betaVariable != null) + { + beta = betaVariable.getDoubleValue(); + } + + double velocity = this.getDoubleValue(); + + double prediction = position + DT * velocity; // xp = x + (dt) v, position prediction + double error = xMeasuredVariable.getDoubleValue() - prediction; + double newPosition = prediction + alpha * error; // x+ = xp + alpha (xmeas - xp), adjusted position estimate + double newVelocity = velocity + beta * error; // v+ = v + beta (xmeas - xp), adjusted velocity estimate + + positionState.set(newPosition); + this.set(newVelocity); // filter updates its YoVariable value to velocity by convention. + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilterTools.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilterTools.java new file mode 100644 index 00000000..6f09dbe3 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilterTools.java @@ -0,0 +1,40 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.AngleTools; +import us.ihmc.commons.MathTools; + +public class AlphaFilterTools +{ + /** + * This computes the alpha + * variable for an alpha filtered yo variable that is equivalent to a first order low pass frequnecy at filter {@param breakFrequencyInHertz}. + * + *

+ * If this + * calculation is done as part of a double provider, consider using {@link AlphaBasedOnBreakFrequencyProvider}, which only updates the alpha estimate + * on a change in the break frequency, saving computation. + *

+ * + * @return alpha value + */ + public static double computeAlphaGivenBreakFrequencyProperly(double breakFrequencyInHertz, double dt) + { + if (Double.isInfinite(breakFrequencyInHertz)) + return 0.0; + + double omega = AngleTools.TwoPI * breakFrequencyInHertz; + double alpha = (1.0 - omega * dt / 2.0) / (1.0 + omega * dt / 2.0); + alpha = MathTools.clamp(alpha, 0.0, 1.0); + return alpha; + } + + /** + * This computes the break frequency of a first order low-pass filter given an alpha value. + * + * @return alpha value + */ + public static double computeBreakFrequencyGivenAlpha(double alpha, double dt) + { + return (1.0 - alpha) / (Math.PI * dt * (1.0 + alpha)); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariable.java new file mode 100644 index 00000000..60a1bd07 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariable.java @@ -0,0 +1,139 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +public class AlphaFilteredWrappingYoVariable extends AlphaFilteredYoVariable +{ + public static final double EPSILON = 1e-10; + + private double previousUnfilteredVariable; + private final YoDouble unfilteredVariable; + private final YoDouble unfilteredInRangeVariable; + private final DoubleProvider alphaVariable; + + private final YoDouble temporaryOutputVariable; + private final YoDouble error; + private final double upperLimit; + private final double lowerLimit; + private final double range; + + //wrap the values in [lowerLimit ; upperLimit[ + + public AlphaFilteredWrappingYoVariable(String name, String description, YoRegistry registry, final YoDouble unfilteredVariable, DoubleProvider alphaVariable, double lowerLimit, double upperLimit) + { + super(name, description, registry, alphaVariable); + this.alphaVariable = alphaVariable; + this.upperLimit = upperLimit; + this.lowerLimit = lowerLimit; + this.range = upperLimit - lowerLimit; + this.unfilteredVariable = unfilteredVariable; + + unfilteredInRangeVariable = new YoDouble(name + "UnfilteredInRangeVariable", registry); + temporaryOutputVariable = new YoDouble(name + "TemporaryOutputVariable", registry); + error = new YoDouble(name + "Error", registry); + + } + + @Override + public void update() + { + update(unfilteredVariable.getDoubleValue()); + } + + + @Override + public void update(double currentPosition) + { + if(!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + previousUnfilteredVariable = unfilteredVariable.getDoubleValue(); + + unfilteredVariableModulo(currentPosition); + + temporaryOutputVariable.set(unfilteredInRangeVariable.getDoubleValue()); + set(unfilteredInRangeVariable.getDoubleValue()); + } + else + { + if (!MathTools.epsilonEquals(currentPosition, previousUnfilteredVariable, EPSILON)) + { + previousUnfilteredVariable = currentPosition; + + unfilteredVariableModulo(currentPosition); + + //calculate the error + double standardError = unfilteredInRangeVariable.getDoubleValue() - getDoubleValue(); + double wrappingError; + if(unfilteredInRangeVariable.getDoubleValue() > getDoubleValue()) + { + wrappingError = lowerLimit - getDoubleValue() + unfilteredInRangeVariable.getDoubleValue() - upperLimit; + } + else + { + wrappingError = upperLimit - getDoubleValue() + unfilteredInRangeVariable.getDoubleValue() - lowerLimit; + } + if(Math.abs(standardError) < Math.abs(wrappingError)) + { + error.set(standardError); + } + else + { + error.set(wrappingError); + } + + //determine if wrapping and move the input if necessary + temporaryOutputVariable.set(getDoubleValue()); + if ((getDoubleValue() + error.getDoubleValue()) >= upperLimit) + { + temporaryOutputVariable.set(getDoubleValue() - range); + } + if ((getDoubleValue() + error.getDoubleValue()) < lowerLimit) + { + temporaryOutputVariable.set(getDoubleValue() + range); + } + } + + temporaryOutputVariable.set(alphaVariable.getValue() * temporaryOutputVariable.getDoubleValue() + (1.0 - alphaVariable.getValue()) + * unfilteredInRangeVariable.getDoubleValue()); + + if (temporaryOutputVariable.getDoubleValue() > upperLimit + EPSILON) + { + set(temporaryOutputVariable.getDoubleValue() - range); + } + else if (temporaryOutputVariable.getDoubleValue() <= lowerLimit - EPSILON) + { + set(temporaryOutputVariable.getDoubleValue() + range); + } + else + { + set(temporaryOutputVariable.getDoubleValue()); + } + } + } + + private void unfilteredVariableModulo(double currentPosition) + { + //handle if the input is out of range + boolean rangeNeedsToBeChecked = true; + unfilteredInRangeVariable.set(currentPosition); + + while(rangeNeedsToBeChecked) + { + rangeNeedsToBeChecked = false; + if (unfilteredInRangeVariable.getDoubleValue() >= upperLimit) + { + unfilteredInRangeVariable.set(unfilteredInRangeVariable.getDoubleValue() - range); + rangeNeedsToBeChecked = true; + } + if (unfilteredInRangeVariable.getDoubleValue() < lowerLimit) + { + unfilteredInRangeVariable.set(unfilteredInRangeVariable.getDoubleValue() + range); + rangeNeedsToBeChecked = true; + } + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoMatrix.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoMatrix.java new file mode 100644 index 00000000..47580e57 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoMatrix.java @@ -0,0 +1,88 @@ +package us.ihmc.yoVariables.filters; + +import org.ejml.data.DMatrix; +import org.ejml.data.DMatrixRMaj; +import org.ejml.dense.row.CommonOps_DDRM; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +public class AlphaFilteredYoMatrix extends YoMatrix +{ + private final DMatrixRMaj previous; + private final DMatrixRMaj current; + private final DMatrixRMaj filtered; + + private final YoDouble alpha; + + public AlphaFilteredYoMatrix(String name, double alpha, int numberOfRows, int numberOfColumns, String[] rowNames, String[] columnNames, YoRegistry registry) + { + this(name, null, alpha, numberOfRows, numberOfColumns, rowNames, columnNames, registry); + } + + public AlphaFilteredYoMatrix(String name, String description, double alpha, int numberOfRows, int numberOfColumns, String[] rowNames, String[] columnNames, YoRegistry registry) + { + super(name, description, numberOfRows, numberOfColumns, rowNames, columnNames, registry); + this.alpha = new YoDouble(name + "_alpha", registry); + this.alpha.set(alpha); + + previous = new DMatrixRMaj(numberOfRows, numberOfColumns); + current = new DMatrixRMaj(numberOfRows, numberOfColumns); + filtered = new DMatrixRMaj(numberOfRows, numberOfColumns); + } + + public void setAlpha(double alpha) + { + this.alpha.set(alpha); + } + + /** + * Set the current value of the matrix to be filtered. + *

+ * NOTE: This method does not solve for the filtered value. To solve for the filtered value, use {@link #solve()}. + *

+ * + * @param current the current value of the matrix to be filtered. Not modified. + */ + @Override + public void set(DMatrix current) + { + super.set(current); + this.current.set(current); + } + + /** + * Assuming that the current value has been set, this method solves for the filtered value. + *

+ * See {@link #set(DMatrix)} for how to set the matrix's current value. + *

+ */ + public void solve() + { + CommonOps_DDRM.scale(alpha.getDoubleValue(), previous, filtered); + + super.get(current); + CommonOps_DDRM.addEquals(filtered, 1 - alpha.getDoubleValue(), current); + + // Set the previous value to be the output of the filter, so it can be used next time + previous.set(filtered); + super.set(filtered); + } + + /** + * Set the current value of the matrix to be filtered and solve for the filtered value. + * + * @param current the current value of the matrix to be filtered. Not modified. + */ + public void setAndSolve(DMatrix current) + { + CommonOps_DDRM.scale(alpha.getDoubleValue(), previous, filtered); + + super.set(current); + this.current.set(current); + CommonOps_DDRM.addEquals(filtered, 1 - alpha.getDoubleValue(), this.current); + + // Set the previous value to be the output of the filter, so it can be used next time + previous.set(filtered); + super.set(filtered); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariable.java new file mode 100644 index 00000000..0a0bfa32 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariable.java @@ -0,0 +1,173 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.AngleTools; +import us.ihmc.commons.MathTools; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * @author jrebula + *

+ *

+ * LittleDogVersion06: + * us.ihmc.LearningLocomotion.Version06.util.YoAlphaFilteredVariable, + * 9:34:00 AM, Aug 29, 2006 + *

= + *

+ * A YoAlphaFilteredVariable is a filtered version of an input YoVar. + * Either a YoVariable holding the unfiltered val is passed in to the + * constructor and update() is called every tick, or update(double) is + * called every tick. The YoAlphaFilteredVariable updates it's val + * with the current filtered version using + *

+ *
+ *            filtered_{n} = alpha * filtered_{n-1} + (1 - alpha) * raw_{n}
+ *         
+ * + * For alpha=0 -> no filtered + * For alpha=1 -> 100% filtered, no use of raw signal + */ +public class AlphaFilteredYoVariable extends YoDouble implements ProcessingYoVariable +{ + private final DoubleProvider alphaVariable; + + private final YoDouble position; + protected final YoBoolean hasBeenCalled; + + /** + * Please use + * @param namePrefix + * @param initialValue + * @param registry + * @return + */ + @Deprecated + public static DoubleProvider createAlphaYoDouble(String namePrefix, double initialValue, YoRegistry registry) + { + return VariableTools.createAlphaYoDouble(namePrefix, "", initialValue, registry); + } + + + public AlphaFilteredYoVariable(String name, YoRegistry registry, double alpha) + { + this(name, registry, alpha, null); + } + + public AlphaFilteredYoVariable(String name, YoRegistry registry, double alpha, YoDouble positionVariable) + { + this(name, "", registry, VariableTools.createAlphaYoDouble(name, "", alpha, registry), positionVariable); + } + + public AlphaFilteredYoVariable(String name, YoRegistry registry, DoubleProvider alphaVariable) + { + this(name, "", registry, alphaVariable, null); + } + + public AlphaFilteredYoVariable(String name, String description, YoRegistry registry, DoubleProvider alphaVariable) + { + this(name, description, registry, alphaVariable, null); + } + + public AlphaFilteredYoVariable(String name, YoRegistry registry, DoubleProvider alphaVariable, YoDouble positionVariable) + { + this(name, "", registry, alphaVariable, positionVariable); + } + + public AlphaFilteredYoVariable(String name, String description, YoRegistry registry, DoubleProvider alphaVariable, YoDouble positionVariable) + { + super(name, description, registry); + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + this.position = positionVariable; + + if (alphaVariable == null) + alphaVariable = VariableTools.createAlphaYoDouble(name, "", 0.0, registry); + this.alphaVariable = alphaVariable; + + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + @Override + public void update() + { + if (position == null) + { + throw new NullPointerException("YoAlphaFilteredVariable must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(position.getDoubleValue()); + } + + public void update(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + set(currentPosition); + } + else + { + double alpha = alphaVariable.getValue(); + set(EuclidCoreTools.interpolate(currentPosition, getDoubleValue(), alpha)); + } + } + + /** + * This method is replaced by computeAlphaGivenBreakFrequencyProperly. It is fine to keep using this method is currently using it, knowing that + * the actual break frequency is not exactly what you are asking for. + * + * @param breakFrequencyInHertz + * @param dt + * @return + */ + @Deprecated + public static double computeAlphaGivenBreakFrequency(double breakFrequencyInHertz, double dt) + { + if (Double.isInfinite(breakFrequencyInHertz)) + return 0.0; + + double alpha = 1.0 - breakFrequencyInHertz * AngleTools.TwoPI * dt; + + alpha = MathTools.clamp(alpha, 0.0, 1.0); + + return alpha; + } + + /** + * This method has been replaced with {@link AlphaFilterTools#computeAlphaGivenBreakFrequencyProperly(double, double)}. It computes the alpha + * variable for an alpha filtered yo variable that is equivalent to a first order low pass frequnecy at filter {@param breakFrequencyInHertz} + * @return alpha value + */ + @Deprecated + public static double computeAlphaGivenBreakFrequencyProperly(double breakFrequencyInHertz, double dt) + { + return AlphaFilterTools.computeAlphaGivenBreakFrequencyProperly(breakFrequencyInHertz, dt); + } + + /** + * This method has been replaced with {@link AlphaFilterTools#computeBreakFrequencyGivenAlpha(double, double)}. + * This computes the break frequency of a first order low-pass filter given an alpha value. + * + * @return alpha value + */ + @Deprecated + public static double computeBreakFrequencyGivenAlpha(double alpha, double dt) + { + return AlphaFilterTools.computeBreakFrequencyGivenAlpha(alpha, dt); + } + + public boolean getHasBeenCalled() + { + return hasBeenCalled.getBooleanValue(); + } + +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariable.java new file mode 100644 index 00000000..768fcabe --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariable.java @@ -0,0 +1,202 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoEnum; + +/** + * This variable is designed to compute the velocity of a position signal that may contain backlash. It computes the velocity of the position using a + * {@link FilteredFiniteDifferenceYoVariable}. When that velocity changes sign, the input is computed to have changed directions, and it enters a "slop" period. + * During the slop period, the output of this variable returns the previous velocity value, which is slowly scaled towards the new measured value over the course of + * the slop duration. + */ +public class BacklashCompensatingVelocityYoVariable extends YoDouble implements ProcessingYoVariable +{ + private final double dt; + + private final FilteredFiniteDifferenceYoVariable finiteDifferenceVelocity; + + private final DoubleProvider alphaVariable; + private final DoubleProvider position; + + private final YoDouble lastPosition; + private final YoBoolean hasBeenCalled; + + private final YoEnum backlashState; + private final DoubleProvider slopTime; + + private final YoDouble timeSinceSloppy; + + public BacklashCompensatingVelocityYoVariable(String name, + String description, + DoubleProvider alphaVariable, + DoubleProvider positionVariable, + double dt, + DoubleProvider slopTime, + YoRegistry registry) + { + + super(name, description, registry); + finiteDifferenceVelocity = new FilteredFiniteDifferenceYoVariable(name + "finiteDifferenceVelocity", "", alphaVariable, dt, registry); + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + + backlashState = new YoEnum<>(name + "BacklashState", registry, BacklashState.class, true); + backlashState.set(null); + timeSinceSloppy = new YoDouble(name + "TimeSinceSloppy", registry); + + position = positionVariable; + + this.alphaVariable = alphaVariable; + this.slopTime = slopTime; + + this.dt = dt; + + lastPosition = new YoDouble(name + "_lastPosition", registry); + + reset(); + } + + public BacklashCompensatingVelocityYoVariable(String name, + String description, + YoDouble alphaVariable, + double dt, + YoDouble slopTime, + YoRegistry registry) + { + this(name, description, alphaVariable, null, dt, slopTime, registry); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + backlashState.set(null); + } + + @Override + public void update() + { + if (position == null) + { + throw new NullPointerException( + "BacklashCompensatingVelocityYoVariable must be constructed with a non null position variable to call update(), otherwise use update(double)"); + } + + update(position.getValue()); + } + + public void update(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + lastPosition.set(currentPosition); + set(0.0); + } + + finiteDifferenceVelocity.update(currentPosition); + double velocityFromFiniteDifferences = finiteDifferenceVelocity.getDoubleValue(); + + // increment time. + timeSinceSloppy.add(dt); + + // No estimate of the backlash state is available, so initialize it based on the current estimated velocity. + if (backlashState.getEnumValue() == null) + { + if (velocityFromFiniteDifferences < 0.0) + backlashState.set(BacklashState.BACKWARD_OK); + else if (velocityFromFiniteDifferences > 0.0) + backlashState.set(BacklashState.FORWARD_OK); + } + else + { + switch (backlashState.getEnumValue()) + { + case BACKWARD_OK: + { + if (velocityFromFiniteDifferences > 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.FORWARD_SLOP); + } + + break; + } + + case FORWARD_OK: + { + if (velocityFromFiniteDifferences < 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.BACKWARD_SLOP); + } + + break; + } + + case BACKWARD_SLOP: + { + if (velocityFromFiniteDifferences > 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.FORWARD_SLOP); + } + else if (timeSinceSloppy.getDoubleValue() > slopTime.getValue()) + { + backlashState.set(BacklashState.BACKWARD_OK); + timeSinceSloppy.set(0.0); + } + + break; + } + + case FORWARD_SLOP: + { + if (velocityFromFiniteDifferences < 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.BACKWARD_SLOP); + } + else if (timeSinceSloppy.getDoubleValue() > slopTime.getValue()) + { + backlashState.set(BacklashState.FORWARD_OK); + timeSinceSloppy.set(0.0); + } + + break; + } + } + } + + double difference = currentPosition - lastPosition.getDoubleValue(); + + // During the slop period, we want to increase the difference from 0 to 1, so that there is no velocity at the beginning of slop, and the velocity is + // increased to the current finite differenced value at the end of the slop period. + if (backlashState.getEnumValue() != null && backlashState.getEnumValue().isInBacklash()) + { + double alpha = timeSinceSloppy.getDoubleValue() / slopTime.getValue(); + alpha = MathTools.clamp(alpha, 0.0, 1.0); + if (Double.isNaN(alpha)) + alpha = 1.0; + + difference = alpha * difference; + } + + // The difference is then finite differenced and alpha filtered + updateUsingDifference(difference); + lastPosition.set(currentPosition); + } + + private void updateUsingDifference(double difference) + { + double previousFilteredDerivative = getDoubleValue(); + double currentRawDerivative = difference / dt; + + this.set(EuclidCoreTools.interpolate(currentRawDerivative, previousFilteredDerivative, alphaVariable.getValue())); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariable.java new file mode 100644 index 00000000..8f8f6161 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariable.java @@ -0,0 +1,150 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoEnum; + +/** + * This class is designed to perform an estimate of a velocity signal that may contain backlash. It does essentially the same as + * {@link BacklashCompensatingVelocityYoVariable}, except it takes a velocity signal as input. + * + * It works by zeroing out the estimated velocity whenever the finite-differenced velocity changes sign. It then ramps this value back up to the value returned + * by finite estimating over the course of the slop duration. It assumes that the slop time is some fixed, constant period when the estimate is unreliable. + */ +public class BacklashProcessingYoVariable extends YoDouble implements ProcessingYoVariable +{ + private final YoDouble velocity; + + private final YoBoolean hasBeenCalled; + + private final YoEnum backlashState; + private final DoubleProvider slopTime; + + private final YoDouble timeSinceSloppy; + + private final double dt; + + public BacklashProcessingYoVariable(String name, String description, double dt, DoubleProvider slopTime, YoRegistry registry) + { + this(name, description, null, dt, slopTime, registry); + } + + public BacklashProcessingYoVariable(String name, String description, YoDouble velocityVariable, double dt, DoubleProvider slopTime, YoRegistry registry) + { + super(name, description, registry); + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + + backlashState = new YoEnum<>(name + "BacklashState", registry, BacklashState.class, true); + backlashState.set(null); + timeSinceSloppy = new YoDouble(name + "TimeSinceSloppy", registry); + + velocity = velocityVariable; + + this.slopTime = slopTime; + + this.dt = dt; + + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + backlashState.set(null); + } + + @Override + public void update() + { + if (velocity == null) + { + throw new NullPointerException( + "BacklashProcessingYoVariable must be constructed with a non null velocity variable to call update(), otherwise use update(double)"); + } + + update(velocity.getDoubleValue()); + } + + public void update(double currentVelocity) + { + if (backlashState.getEnumValue() == null) + { + backlashState.set(BacklashState.FORWARD_OK); + } + + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + set(currentVelocity); + } + + timeSinceSloppy.add(dt); + + switch (backlashState.getEnumValue()) + { + case BACKWARD_OK: + { + if (currentVelocity > 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.FORWARD_SLOP); + } + + break; + } + + case FORWARD_OK: + { + if (currentVelocity < 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.BACKWARD_SLOP); + } + + break; + } + + case BACKWARD_SLOP: + { + if (currentVelocity > 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.FORWARD_SLOP); + } + else if (timeSinceSloppy.getDoubleValue() > slopTime.getValue()) + { + backlashState.set(BacklashState.BACKWARD_OK); + } + + break; + } + + case FORWARD_SLOP: + { + if (currentVelocity < 0.0) + { + timeSinceSloppy.set(0.0); + backlashState.set(BacklashState.BACKWARD_SLOP); + } + else if (timeSinceSloppy.getDoubleValue() > slopTime.getValue()) + { + backlashState.set(BacklashState.FORWARD_OK); + } + + break; + } + } + + double percent = timeSinceSloppy.getDoubleValue() / slopTime.getValue(); + percent = MathTools.clamp(percent, 0.0, 1.0); + if (Double.isNaN(percent) || slopTime.getValue() < dt) + percent = 1.0; + + this.set(percent * currentVelocity); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/BacklashState.java b/src/filters/java/us/ihmc/yoVariables/filters/BacklashState.java new file mode 100644 index 00000000..29744ce7 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/BacklashState.java @@ -0,0 +1,18 @@ +package us.ihmc.yoVariables.filters; + +enum BacklashState +{ + BACKWARD_OK(false), FORWARD_OK(false), BACKWARD_SLOP(true), FORWARD_SLOP(true); + + private final boolean isInBacklash; + + private BacklashState(boolean inBacklash) + { + this.isInBacklash = inBacklash; + } + + public boolean isInBacklash() + { + return isInBacklash; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariable.java new file mode 100644 index 00000000..7622c3ce --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariable.java @@ -0,0 +1,152 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * @author jrebula + *

+ *

+ * LittleDogVersion06: us.ihmc.LearningLocomotion.Version06.util.YoAlphaFilteredVariable, + * 9:34:00 AM, Aug 29, 2006 + *

+ *

+ * A YoAlphaFilteredVariable is a filtered version of an input YoVar. Either a YoVariable + * holding the unfiltered val is passed in to the constructor and update() is called every + * tick, or update(double) is called every tick. The YoAlphaFilteredVariable updates it's + * val with the current filtered version using + *

+ * + *
+ *            filtered_{n} = alpha * filtered_{n-1} + 1/2 * (1 - alpha) * (raw_{n} + raw{n-1}}
+ *         
+ */ +public class ButterworthFilteredYoVariable extends YoDouble +{ + public enum ButterworthFilterType + { + LOW_PASS, HIGH_PASS + } + + private final DoubleProvider alphaVariable; + private final ButterworthFilterType butterworthFilterType; + + private final DoubleProvider position; + private final YoDouble previousInput; + + private final YoBoolean hasBeenCalled; + + public ButterworthFilteredYoVariable(String name, YoRegistry registry, double alpha, ButterworthFilterType butterworthFilterType) + { + this(name, registry, AlphaFilteredYoVariable.createAlphaYoDouble(name, alpha, registry), butterworthFilterType); + } + + public ButterworthFilteredYoVariable(String name, + YoRegistry registry, + double alpha, + DoubleProvider positionVariable, + ButterworthFilterType butterworthFilterType) + { + this(name, registry, AlphaFilteredYoVariable.createAlphaYoDouble(name, alpha, registry), positionVariable, butterworthFilterType); + } + + public ButterworthFilteredYoVariable(String name, YoRegistry registry, DoubleProvider alphaVariable, ButterworthFilterType butterworthFilterType) + { + this(name, registry, alphaVariable, null, butterworthFilterType); + } + + public ButterworthFilteredYoVariable(String name, + YoRegistry registry, + DoubleProvider alphaVariable, + DoubleProvider positionVariable, + ButterworthFilterType butterworthFilterType) + { + super(name, registry); + + this.alphaVariable = alphaVariable; + this.butterworthFilterType = butterworthFilterType; + + this.position = positionVariable; + this.previousInput = new YoDouble(name + "_prevIn", registry); + this.hasBeenCalled = new YoBoolean(name + "HasBeenCalled", registry); + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + } + + public void update() + { + if (position == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(position.getValue()); + } + + public void update(double currentInput) + { + if (!hasBeenCalled.getValue()) + { + hasBeenCalled.set(true); + + if (this.butterworthFilterType == ButterworthFilterType.HIGH_PASS) + { + set(0.0); + } + else + { + set(currentInput); + } + } + else + { + double alpha = alphaVariable.getValue(); + + switch (butterworthFilterType) + { + case LOW_PASS: + { + set(alpha * getValue() + 0.5 * (1.0 - alpha) * (currentInput + previousInput.getValue())); + break; + } + case HIGH_PASS: + { + set(alpha * getValue() + 0.5 * (1.0 + alpha) * (currentInput - previousInput.getValue())); + break; + } + } + } + + previousInput.set(currentInput); + } + + public static double computeAlphaGivenBreakFrequency(double breakFrequencyInHertz, double dt) + { + if (Double.isInfinite(breakFrequencyInHertz)) + return 0.0; + + double samplingFrequency = 1.0 / dt; + + if (breakFrequencyInHertz > 0.25 * samplingFrequency) + return 0.0; + + double tanOmegaBreak = Math.tan(Math.PI * breakFrequencyInHertz * dt); + return MathTools.clamp((1.0 - tanOmegaBreak) / (1.0 + tanOmegaBreak), 0.0, 1.0); + } + + public static double computeBreakFrequencyGivenAlpha(double alpha, double dt) + { + double beta = (1.0 - alpha) / (1.0 + alpha); + return Math.max(Math.atan(beta) / (dt * Math.PI), 0.0); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/DeadbandedYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/DeadbandedYoVariable.java new file mode 100644 index 00000000..0ec1cf4a --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/DeadbandedYoVariable.java @@ -0,0 +1,60 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.DeadbandTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +public class DeadbandedYoVariable extends YoDouble implements ProcessingYoVariable +{ + private final DoubleProvider deadzoneSize; + private final DoubleProvider inputVariable; + + public DeadbandedYoVariable(String name, DoubleProvider deadzoneSize, YoRegistry registry) + { + super(name, registry); + this.inputVariable = null; + this.deadzoneSize = deadzoneSize; + } + + public DeadbandedYoVariable(String name, DoubleProvider inputVariable, DoubleProvider deadzoneSize, YoRegistry registry) + { + super(name, registry); + this.inputVariable = inputVariable; + this.deadzoneSize = deadzoneSize; + } + + public void update() + { + if (inputVariable == null) + throw new NullPointerException("DeadzoneYoVariable must be constructed with a non null " + + "input variable to call update(), otherwise use update(double)"); + update(inputVariable.getValue()); + } + + public void update(double valueToBeCorrected) + { + super.set(DeadbandTools.applyDeadband(deadzoneSize.getValue(), valueToBeCorrected)); + } + + public static void main(String[] args) + { + YoRegistry registry = new YoRegistry("test"); + YoDouble deadzoneSize = new YoDouble("deadzoneSize", registry); + YoDouble input = new YoDouble("input", registry); + deadzoneSize.set(2.0); + DeadbandedYoVariable testDeadzone = new DeadbandedYoVariable("testDeadZone", input, deadzoneSize, registry); + + for(int i = -50; i < 51; i++) + { + input.set((double)i); + testDeadzone.update(); + System.out.println("//////////////////////////"); + System.out.println("uncorrected = " + (double)i); + System.out.println("corrected = " + testDeadzone.getDoubleValue()); + } + + System.out.println("done"); + } + +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoBoolean.java b/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoBoolean.java new file mode 100644 index 00000000..3d18c14f --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoBoolean.java @@ -0,0 +1,72 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +/** + * This is a class that introduces purposeful delay between the passed in variable and the output of this variable. This is useful to capture latency in a + * process. The length of the delay is set by a specific number of ticks. + */ +public class DelayedYoBoolean extends YoBoolean +{ + private final YoBoolean variableToDelay; + + private final YoBoolean[] previousYoVariables; + + public DelayedYoBoolean(String name, String description, YoBoolean variableToDelay, int ticksToDelay, YoRegistry registry) + { + super(name, description, registry); + + this.variableToDelay = variableToDelay; + previousYoVariables = new YoBoolean[ticksToDelay]; + + for (int i = 0; i < ticksToDelay; i++) + { + previousYoVariables[i] = new YoBoolean(name + "_previous" + i, registry); + previousYoVariables[i].set(variableToDelay.getBooleanValue()); + } + + this.set(variableToDelay.getBooleanValue()); + } + + public void update() + { + if (previousYoVariables.length == 0) + { + this.set(variableToDelay.getBooleanValue()); + return; + } + + this.set(previousYoVariables[0].getBooleanValue()); + + for (int i = 0; i < previousYoVariables.length - 1; i++) + { + previousYoVariables[i].set(previousYoVariables[i + 1].getBooleanValue()); + } + + previousYoVariables[previousYoVariables.length - 1].set(variableToDelay.getBooleanValue()); + } + + public void reset() + { + for (YoBoolean var : previousYoVariables) + { + var.set(variableToDelay.getBooleanValue()); + } + this.set(variableToDelay.getBooleanValue()); + } + + void getInternalState(String inputString, Boolean ifDebug) + { + if (!ifDebug) + return; + + String string = inputString + "\nvalue = " + this.getBooleanValue() + "\n"; + for (int i = 0; i < previousYoVariables.length; i++) + { + string = string + i + " = " + previousYoVariables[i].getBooleanValue() + "\n"; + } + string = string + "variableToDelay = " + variableToDelay.getBooleanValue() + "\n"; + System.out.println(string); + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoDouble.java new file mode 100644 index 00000000..47301666 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/DelayedYoDouble.java @@ -0,0 +1,59 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * This is a class that introduces purposeful delay between the passed in variable and the output of this variable. This is useful to capture latency in a + * process. The length of the delay is set by a specific number of ticks. + */ +public class DelayedYoDouble extends YoDouble +{ + private final DoubleProvider variableToDelay; + + private final YoDouble[] previousYoDouble; + + public DelayedYoDouble(String name, String description, DoubleProvider variableToDelay, int ticksToDelay, YoRegistry registry) + { + super(name, description, registry); + + this.variableToDelay = variableToDelay; + previousYoDouble = new YoDouble[ticksToDelay]; + + for (int i = 0; i < ticksToDelay; i++) + { + previousYoDouble[i] = new YoDouble(name + "_previous" + i, registry); + previousYoDouble[i].set(variableToDelay.getValue()); + } + + this.set(variableToDelay.getValue()); + } + + public void update() + { + if (previousYoDouble.length == 0) + { + this.set(variableToDelay.getValue()); + return; + } + + this.set(previousYoDouble[0].getValue()); + + for (int i = 0; i < previousYoDouble.length - 1; i++) + { + previousYoDouble[i].set(previousYoDouble[i + 1].getValue()); + } + + previousYoDouble[previousYoDouble.length - 1].set(variableToDelay.getValue()); + } + + public void reset() + { + for (YoDouble var : previousYoDouble) + { + var.set(variableToDelay.getValue()); + } + this.set(variableToDelay.getValue()); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariable.java new file mode 100644 index 00000000..688363d1 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariable.java @@ -0,0 +1,68 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * This variable stores data in such a way that its value is guaranteed to be within a certain delta of the desired value. + */ +public class DeltaLimitedYoVariable extends YoDouble +{ + private final YoDouble maxDelta; + private final YoDouble actual; + private final YoDouble desired; + private final YoBoolean isLimitingActive; + + public DeltaLimitedYoVariable(String name, YoRegistry registry, double maxDelta) + { + super(name, registry); + + this.maxDelta = new YoDouble(name + "MaxAllowedDelta", registry); + this.maxDelta.set(Math.abs(maxDelta)); + this.actual = new YoDouble(name + "Actual", registry); + this.desired = new YoDouble(name + "Desired", registry); + this.isLimitingActive = new YoBoolean(name + "IsLimitingActive", registry); + isLimitingActive.set(false); + } + + public void setMaxDelta(double maxDelta) + { + this.maxDelta.set(Math.abs(maxDelta)); + } + + public void updateOutput(double actual, double desired) + { + this.desired.set(desired); + this.actual.set(actual); + updateOutput(); + } + + public boolean isLimitingActive() + { + return isLimitingActive.getBooleanValue(); + } + + private void updateOutput() + { + double actualDoubleValue = actual.getDoubleValue(); + double desiredDoubleValue = desired.getDoubleValue(); + double maxDeltaDoubleValue = Math.abs(maxDelta.getDoubleValue()); + double rawDelta = actualDoubleValue - desiredDoubleValue; + double sign = Math.signum(rawDelta); + double requestedDelta = Math.abs(rawDelta); + double overshoot = maxDeltaDoubleValue - requestedDelta; + + if(overshoot < 0) + { + desiredDoubleValue = actualDoubleValue - maxDeltaDoubleValue * sign; + isLimitingActive.set(true); + } + else + { + isLimitingActive.set(false); + } + + this.set(desiredDoubleValue); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariable.java new file mode 100644 index 00000000..e548ae57 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariable.java @@ -0,0 +1,163 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoEnum; + +/** + * @author jrebula + *

+ * A {@link FilteredDiscreteVelocityYoVariable} is a filtered velocity of a position. That is, it takes the finite difference of the position signal to + * compute a velocity, it then filters the velocity using an alpha filter + * Either a YoVariable holding the position is passed in to the + * constructor and update() is called every tick, or update(double) is + * called every tick. The YoFilteredVelocityVariable updates it's val + * with the current velocity after a filter of + *

+ * + *
+ *            vel_{n} = alpha * vel{n-1} + (1 - alpha) * (pos_{n} - pos_{n-1})
+ * 
+ * + */ +public class FilteredDiscreteVelocityYoVariable extends YoDouble +{ + + private final YoDouble time; + + private final DoubleProvider alphaVariable; + private final YoDouble position; + + private final YoDouble lastUpdateTime; + private final YoEnum lastUpdateDirection; + private final YoDouble unfilteredVelocity; + + private final YoDouble lastPosition; + private boolean hasBeenCalled; + + public FilteredDiscreteVelocityYoVariable(String name, String description, double alpha, YoDouble positionVariable, YoDouble time, + YoRegistry registry) + { + this(name, description, VariableTools.createAlphaYoDouble(name, "", alpha, registry), positionVariable, time, registry); + } + + public FilteredDiscreteVelocityYoVariable(String name, String description, DoubleProvider alphaVariable, YoDouble positionVariable, + YoDouble time, YoRegistry registry) + { + super(name, description, registry); + position = positionVariable; + this.alphaVariable = alphaVariable; + + this.time = time; + + lastPosition = new YoDouble(name + "_lastPosition", registry); + lastUpdateTime = new YoDouble(name + "_lastUpdateTime", registry); + lastUpdateDirection = new YoEnum<>(name + "_lastUpdateDirection", registry, Direction.class); + unfilteredVelocity = new YoDouble(name + "_unfilteredVelocity", registry); + + reset(); + } + + public void reset() + { + hasBeenCalled = false; + } + + public void update() + { + if (position == null) + { + throw new NullPointerException("YoFilteredVelocityVariable must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(position.getDoubleValue()); + } + + public void update(double currentPosition) + { + if (!hasBeenCalled) + { + hasBeenCalled = true; + + // lastPosition = currentPosition; + lastPosition.set(currentPosition); + lastUpdateTime.set(time.getDoubleValue()); + lastUpdateDirection.set(Direction.NONE); + } + + // Figure out if the count changed and if so, if the direction changed or not and then update the direction: + boolean countChanged = false; + if (currentPosition != lastPosition.getDoubleValue()) + countChanged = true; + + // If the count changed, figure out if the direction changed: + + boolean directionChanged = false; + if (countChanged) + { + if (currentPosition > lastPosition.getDoubleValue()) + { + if (lastUpdateDirection.getEnumValue() != Direction.FORWARD) + directionChanged = true; + lastUpdateDirection.set(Direction.FORWARD); + } + else if (currentPosition < lastPosition.getDoubleValue()) + { + if (lastUpdateDirection.getEnumValue() != Direction.BACKWARD) + directionChanged = true; + lastUpdateDirection.set(Direction.BACKWARD); + } + } + + // If the direction changed, then the velocity is set to zero: + if (directionChanged) + { + unfilteredVelocity.set(0.0); + } + + // If the direction hasn't changed, but the count changed then compute the velocity based on the time since last update: + else if (countChanged) + { + double diffTime = time.getDoubleValue() - lastUpdateTime.getDoubleValue(); + if (diffTime < 1e-7) + unfilteredVelocity.set(0.0); + else + { + unfilteredVelocity.set((currentPosition - lastPosition.getDoubleValue()) / diffTime); + } + } + + else + { + // If the count hasn't changed, then not quite sure what the velocity is. + // We could just use the current velocity, but really should try to figure out if things have been slowing down or not. + // For now, multiply by some largish fraction, just to make sure it does trail off to zero if the velocity stops quickly. + unfilteredVelocity.set(0.99 * unfilteredVelocity.getDoubleValue()); + } + + // Low pass alpha filter it... + set(EuclidCoreTools.interpolate(unfilteredVelocity.getDoubleValue(), getDoubleValue(), alphaVariable.getValue())); + + // Remember the position and the currentTime if the countChanged: + + if (countChanged) + { + lastPosition.set(currentPosition); + lastUpdateTime.set(time.getDoubleValue()); + } + } + + public double getUnfilteredVelocity() + { + return unfilteredVelocity.getDoubleValue(); + } + + private enum Direction + { + NONE, FORWARD, BACKWARD; + } + +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/FilteredFiniteDifferenceYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/FilteredFiniteDifferenceYoVariable.java new file mode 100644 index 00000000..c6e8d703 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/FilteredFiniteDifferenceYoVariable.java @@ -0,0 +1,141 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + *

+ * A {@link FilteredFiniteDifferenceYoVariable} computes the velocity of a position signal via finite differencing it, and then applies an alpha filter to that + * value. The value contained in the parent {@link YoDouble} returns the filtered velocity signal. + *

+ *

+ * To construct and use this variable, either a YoVariable holding the position is passed in at construction + * constructor and {@link #update()} is called every tick, or {@link #update(double)} is + * called every tick. + *

+ *

The {@link FilteredFiniteDifferenceYoVariable} updates its value with the current velocity after a filter of + *

+ * + *
+ *                  vel_{n} = alpha * vel{n-1} + (1 - alpha) * (pos_{n} - pos_{n-1})
+ *       
+ */ +public class FilteredFiniteDifferenceYoVariable extends YoDouble implements ProcessingYoVariable +{ + private final double dt; + + private final DoubleProvider alphaVariable; + private final DoubleProvider position; + + // private double lastPosition; + private final YoDouble lastPosition; + private final YoBoolean hasBeenCalled; + + public FilteredFiniteDifferenceYoVariable(String name, String description, double alpha, double dt, YoRegistry registry) + { + this(name, description, alpha, null, dt, registry); + } + + public FilteredFiniteDifferenceYoVariable(String name, String description, double alpha, DoubleProvider positionVariable, double dt, YoRegistry registry) + { + this(name, description, VariableTools.createAlphaYoDouble(name, "", alpha, registry), positionVariable, dt, registry); + } + + public FilteredFiniteDifferenceYoVariable(String name, String description, DoubleProvider alphaVariable, double dt, YoRegistry registry) + { + this(name, description, alphaVariable, null, dt, registry); + } + + public FilteredFiniteDifferenceYoVariable(String name, + String description, + DoubleProvider alphaVariable, + DoubleProvider positionVariable, + double dt, + YoRegistry registry) + { + super(name, description, registry); + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + + position = positionVariable; + this.alphaVariable = alphaVariable; + + this.dt = dt; + + lastPosition = new YoDouble(name + "_lastPosition", registry); + + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + } + + @Override + public void update() + { + if (position == null) + { + throw new NullPointerException( + "YoFilteredVelocityVariable must be constructed with a non null " + "position variable to call update(), otherwise use update(double)"); + } + + update(position.getValue()); + } + + public void updateForAngles() + { + if (position == null) + { + throw new NullPointerException( + "YoFilteredVelocityVariable must be constructed with a non null " + "position variable to call update(), otherwise use update(double)"); + } + + updateForAngles(position.getValue()); + } + + public void update(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + lastPosition.set(currentPosition); + set(0.0); + } + + double difference = currentPosition - lastPosition.getDoubleValue(); + + updateUsingDifference(difference); + + lastPosition.set(currentPosition); + } + + public void updateForAngles(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + lastPosition.set(currentPosition); + set(0.0); + } + + double difference = EuclidCoreTools.angleDifferenceMinusPiToPi(currentPosition, lastPosition.getDoubleValue()); + + updateUsingDifference(difference); + + lastPosition.set(currentPosition); + } + + private void updateUsingDifference(double difference) + { + double previousFilteredDerivative = getDoubleValue(); + double currentRawDerivative = difference / dt; + + double alpha = alphaVariable.getValue(); + set(alpha * previousFilteredDerivative + (1.0 - alpha) * currentRawDerivative); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderBandPassFilteredYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderBandPassFilteredYoDouble.java new file mode 100644 index 00000000..3599edc7 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderBandPassFilteredYoDouble.java @@ -0,0 +1,70 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +public class FirstOrderBandPassFilteredYoDouble extends FirstOrderFilteredYoDouble +{ + private boolean hasBeenCalled = false; + + private final FirstOrderFilteredYoDouble highPassFilteredInput; + + public FirstOrderBandPassFilteredYoDouble(String name, String description, double minPassThroughFrequency_Hz, double maxPassThroughFrequency_Hz, + YoDouble yoTime, YoRegistry registry) + { + super(name, description, maxPassThroughFrequency_Hz, yoTime, FirstOrderFilteredYoDouble.FirstOrderFilterType.LOW_PASS, registry); + + this.highPassFilteredInput = new FirstOrderFilteredYoDouble(name + "HighPassFilteredOnly", description, minPassThroughFrequency_Hz, yoTime, + FirstOrderFilteredYoDouble.FirstOrderFilterType.HIGH_PASS, registry); + + setPassBand(minPassThroughFrequency_Hz, maxPassThroughFrequency_Hz); + } + + public FirstOrderBandPassFilteredYoDouble(String name, String description, double minPassThroughFrequency_Hz, double maxPassThroughFrequency_Hz, + double DT, YoRegistry registry) + { + super(name, description, maxPassThroughFrequency_Hz, DT, FirstOrderFilteredYoDouble.FirstOrderFilterType.LOW_PASS, registry); + + this.highPassFilteredInput = new FirstOrderFilteredYoDouble(name + "HighPassFilteredOnly", description, minPassThroughFrequency_Hz, DT, + FirstOrderFilteredYoDouble.FirstOrderFilterType.HIGH_PASS, registry); + } + + private void checkPassband(double minPassThroughFrequency_Hz, double maxPassThroughFrequency_Hz) + { + if (minPassThroughFrequency_Hz > maxPassThroughFrequency_Hz) + { + throw new RuntimeException("minPassThroughFrequency [ " + minPassThroughFrequency_Hz + " ] > maxPassThroughFrequency [ " + maxPassThroughFrequency_Hz + + " ]"); + } + } + + public void update(double filterInput) + { + if (!hasBeenCalled) + { + hasBeenCalled = true; + this.set(filterInput); + } + else + { + updateHighPassFilterAndThenLowPassFilterThat(filterInput); + } + } + + public void setPassBand(double minPassThroughFreqHz, double maxPassThroughFreqHz) + { + checkPassband(minPassThroughFreqHz, maxPassThroughFreqHz); + + highPassFilteredInput.setCutoffFrequencyHz(minPassThroughFreqHz); + this.setCutoffFrequencyHz(maxPassThroughFreqHz); + } + + private void updateHighPassFilterAndThenLowPassFilterThat(double filterInput) + { + this.highPassFilteredInput.update(filterInput); + + super.update(highPassFilteredInput.getDoubleValue()); + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDouble.java new file mode 100644 index 00000000..71e28fd3 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDouble.java @@ -0,0 +1,163 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +public class FirstOrderFilteredYoDouble extends YoDouble +{ + public enum FirstOrderFilterType + { + LOW_PASS, HIGH_PASS + } + + private boolean hasBeenCalled = false; + + private double filterInputOld; + private double filterUpdateTimeOld; + + private final YoDouble cutoffFrequencyHz; + + private final DoubleProvider yoTime; + private double dt; + + private final FirstOrderFilterType filterType; + + public FirstOrderFilteredYoDouble(String name, + String description, + double cutoffFrequencyHz, + DoubleProvider yoTime, + FirstOrderFilterType highOrLowPass, + YoRegistry registry) + { + super(name, description, registry); + + String cutoffFrequencyName; + switch (highOrLowPass) + { + case LOW_PASS: + cutoffFrequencyName = name + "_LowPassCutoff_Hz"; + break; + case HIGH_PASS: + cutoffFrequencyName = name + "_HighPassCutoff_Hz"; + break; + default: + throw new RuntimeException("Must Specify Filter Type as Low or High Pass. Current Specification : " + highOrLowPass); + } + + this.cutoffFrequencyHz = new YoDouble(cutoffFrequencyName, registry); + this.cutoffFrequencyHz.set(cutoffFrequencyHz); + + this.yoTime = yoTime; + + this.filterType = highOrLowPass; + } + + public FirstOrderFilteredYoDouble(String name, + String description, + double cutoffFrequency_Hz, + double dt, + FirstOrderFilterType highOrLowPass, + YoRegistry registry) + { + this(name, description, cutoffFrequency_Hz, null, highOrLowPass, registry); + this.dt = dt; + } + + private double computeLowPassUpdate(double filterInput, double dt) + { + double alpha = computeAlpha(dt, cutoffFrequencyHz.getDoubleValue()); + + return alpha * this.getDoubleValue() + (1.0 - alpha) * filterInput; + } + + private double computeHighPassUpdate(double filterInput, double dt) + { + double alpha = computeAlpha(dt, cutoffFrequencyHz.getDoubleValue()); + + double ret = alpha * (this.getDoubleValue() + filterInput - filterInputOld); + return ret; + } + + private double computeAlpha(double dt, double cutoffFrequencyHz) +{ + if (cutoffFrequencyHz <= 0.0) + { + throw new RuntimeException("Cutoff Frequency must be greater than zero. Cutoff = " + cutoffFrequencyHz); + } + + double cutoff_radPerSec = cutoffFrequencyHz * 2.0 * Math.PI; + double RC = 1.0 / cutoff_radPerSec; + double alpha = RC / (RC + dt); // alpha decreases with increasing cutoff frequency + + if (alpha <= 0 || alpha >= 1.0 && dt != 0.0) + { + throw new RuntimeException("Alpha value must be between 0 and 1. Alpha = " + alpha); + } + + return alpha; +} + + public void reset() + { + hasBeenCalled = false; + } + + public void setCutoffFrequencyHz(double cutoffHz) + { + this.cutoffFrequencyHz.set(cutoffHz); + } + + public void update(double filterInput) + { + if (!hasBeenCalled) + { + hasBeenCalled = true; + + filterInputOld = 0.0; + filterUpdateTimeOld = 0.0; + + this.set(filterInput); + } + else + { + if (yoTime != null) + { + double timeSinceLastUpdate = yoTime.getValue() - filterUpdateTimeOld; + + if (timeSinceLastUpdate > 0.0) + { + dt = timeSinceLastUpdate; + } + else + { + reset(); + // throw new RuntimeException("Computed step size, DT must be greater than zero. DT = " + dt + ". Current time = " + yoTime.getDoubleValue() + ", previous update time = " + filterUpdateTimeOld); + } + } + + double filterOutput; + + switch (filterType) + { + case LOW_PASS: + filterOutput = computeLowPassUpdate(filterInput, dt); + break; + case HIGH_PASS: + filterOutput = computeHighPassUpdate(filterInput, dt); + break; + default: + throw new RuntimeException("The first order filter must be either a high pass or low pass filter, it cannot be " + filterType); + } + + this.set(filterOutput); + } + + filterInputOld = filterInput; + + if (yoTime != null) + { + filterUpdateTimeOld = yoTime.getValue(); + } + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBoolean.java b/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBoolean.java new file mode 100644 index 00000000..c2f6d2f0 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBoolean.java @@ -0,0 +1,92 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoInteger; + +public class GlitchFilteredYoBoolean extends YoBoolean +{ + private final YoBoolean variableToFilter; + private final YoInteger windowSize; + protected final YoInteger counter; + + public GlitchFilteredYoBoolean(String name, YoRegistry registry, int windowSize) + { + this(name, "", registry, null, windowSize); + } + + public GlitchFilteredYoBoolean(String name, YoRegistry registry, YoBoolean yoVariableToFilter, int windowSize) + { + this(name, "", registry, yoVariableToFilter, windowSize); + } + + public GlitchFilteredYoBoolean(String name, YoRegistry registry, YoBoolean yoVariableToFilter, YoInteger windowSize) + { + this(name, "", registry, yoVariableToFilter, windowSize); + } + + public GlitchFilteredYoBoolean(String name, String description, YoRegistry registry, YoBoolean yoVariableToFilter, int windowSize) + { + this(name, description, registry, yoVariableToFilter, VariableTools.createWindowSizeYoInteger(name, "", windowSize, registry)); + } + + public GlitchFilteredYoBoolean(String name, String description, YoRegistry registry, YoBoolean yoVariableToFilter, YoInteger windowSize) + { + super(name, description, registry); + counter = new YoInteger(name + "Count", description, registry); + this.windowSize = windowSize; + + if (windowSize.getIntegerValue() < 0) + throw new RuntimeException("window size must be greater than 0"); + + variableToFilter = yoVariableToFilter; + + if (variableToFilter != null) + this.set(yoVariableToFilter.getBooleanValue()); + + this.set(false); + } + + public boolean set(boolean value) + { + if (counter != null) + { + counter.set(0); + } + return super.set(value); + } + + public void update(boolean value) + { + + if (value != this.getBooleanValue()) + { + counter.set(counter.getIntegerValue() + 1); + } + else + counter.set(0); + + if (counter.getIntegerValue() >= (windowSize.getIntegerValue())) + set(value); + } + + public int getWindowSize() + { + return windowSize.getIntegerValue(); + } + + public void setWindowSize(int windowSize) //untested + { + this.windowSize.set(windowSize); + } + + public void update() + { + if (variableToFilter == null) + { + throw new RuntimeException("variableToFilter was not initialized. Use the other constructor"); + } + else + update(variableToFilter.getBooleanValue()); + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoInteger.java b/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoInteger.java new file mode 100644 index 00000000..469e7a70 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/GlitchFilteredYoInteger.java @@ -0,0 +1,99 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.IntegerProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoInteger; + +public class GlitchFilteredYoInteger extends YoInteger +{ + private final IntegerProvider position; + private final YoInteger previousPosition; + private final IntegerProvider windowSize; + private final YoInteger counter; + + public GlitchFilteredYoInteger(String name, int windowSize, YoRegistry registry) + { + this(name, windowSize, null, registry); + } + + public GlitchFilteredYoInteger(String name, int windowSize, IntegerProvider position, YoRegistry registry) + { + this(name, VariableTools.createWindowSizeYoInteger(name, "", windowSize, registry), position, registry); + } + + public GlitchFilteredYoInteger(String name, IntegerProvider windowSize, YoRegistry registry) + { + this(name, windowSize, null, registry); + } + + public GlitchFilteredYoInteger(String name, IntegerProvider windowSize, IntegerProvider position, YoRegistry registry) + { + super(name, GlitchFilteredYoInteger.class.getSimpleName(), registry); + + this.position = position; + + previousPosition = new YoInteger(name + "PrevValue", registry); + counter = new YoInteger(name + "Count", registry); + this.windowSize = windowSize; + } + + @Override + public boolean set(int value) + { + if (counter != null) + counter.set(0); + return super.set(value); + } + + @Override + public boolean set(int value, boolean notifyListeners) + { + if (counter != null) + counter.set(0); + return super.set(value, notifyListeners); + } + + public void update() + { + if (position == null) + { + throw new NullPointerException( + "GlitchFilteredYoInteger must be constructed with a non null position variable to call update(), otherwise use update(int)"); + } + + update(position.getValue()); + } + + public void update(int currentValue) + { + if (currentValue == previousPosition.getIntegerValue()) + counter.increment(); + else + counter.set(0); + + if (counter.getIntegerValue() >= windowSize.getValue()) + { + set(currentValue); + counter.set(0); + } + + previousPosition.set(currentValue); + } + + public int getWindowSize() + { + return windowSize.getValue(); + } + + public void setWindowSize(int windowSize) + { + if (this.windowSize instanceof YoInteger) + { + ((YoInteger) this.windowSize).set(windowSize); + } + else + { + throw new RuntimeException("Setting the window size is not supported"); + } + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/JerkLimitedYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/JerkLimitedYoDouble.java new file mode 100644 index 00000000..8f66d063 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/JerkLimitedYoDouble.java @@ -0,0 +1,151 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +public class JerkLimitedYoDouble extends YoDouble +{ + private final double dt; + + private final YoBoolean hasBeenInitialized; + + private final YoDouble smoothedRate; + private final YoDouble smoothedAcceleration; + private final YoDouble smoothedJerk; + + private final YoDouble positionGain; + private final YoDouble velocityGain; + private final YoDouble accelerationGain; + + private final YoDouble maximumJerk; + private final YoDouble maximumAcceleration; + + private final YoDouble inputPosition; + private final YoDouble inputVelocity; + private final YoDouble inputAcceleration; + + public JerkLimitedYoDouble(String name, YoRegistry registry, YoDouble maxAcceleration, YoDouble maxJerk, double dt) + { + this(name, registry, maxAcceleration, maxJerk, null, null, null, dt); + } + + public JerkLimitedYoDouble(String name, YoRegistry registry, YoDouble maxAcceleration, YoDouble maxJerk, + YoDouble inputPosition, double dt) + { + this(name, registry, maxAcceleration, maxJerk, inputPosition, null, null, dt); + } + + public JerkLimitedYoDouble(String name, YoRegistry registry, YoDouble maxAcceleration, YoDouble maxJerk, + YoDouble inputPosition, YoDouble inputVelocity, double dt) + { + this(name, registry, maxAcceleration, maxJerk, inputPosition, inputVelocity, null, dt); + } + + public JerkLimitedYoDouble(String name, YoRegistry registry, YoDouble maxAcceleration, YoDouble maxJerk, + YoDouble inputPosition, YoDouble inputVelocity, YoDouble inputAcceleration, double dt) + { + super(name, registry); + + this.inputPosition = inputPosition; + this.inputVelocity = inputVelocity; + this.inputAcceleration = inputAcceleration; + + this.maximumJerk = maxJerk; + this.maximumAcceleration = maxAcceleration; + + this.dt = dt; + + hasBeenInitialized = new YoBoolean(name + "HasBeenInitialized", registry); + + smoothedRate = new YoDouble(name + "SmoothedRate", registry); + smoothedAcceleration = new YoDouble(name + "SmoothedAcceleration", registry); + smoothedJerk = new YoDouble(name + "SmoothedJerk", registry); + + positionGain = new YoDouble(name + "PositionGain", registry); + velocityGain = new YoDouble(name + "VelocityGain", registry); + accelerationGain = new YoDouble(name + "AccelerationGain", registry); + + double w0 = 2.0 * Math.PI * 16.0; + double w1 = 2.0 * Math.PI * 16.0; + double zeta = 1.0; + + setGainsByPolePlacement(w0, w1, zeta); + hasBeenInitialized.set(false); + } + + public void setMaximumAcceleration(double maximumAcceleration) + { + this.maximumAcceleration.set(maximumAcceleration); + } + + public void setMaximumJerk(double maximumJerk) + { + this.maximumJerk.set(maximumJerk); + } + + public void setGainsByPolePlacement(double w0, double w1, double zeta) + { + positionGain.set(w0 * w1 * w1); + velocityGain.set(w1 * w1 + 2.0 * zeta * w1 * w0); + accelerationGain.set(w0 + 2.0 * zeta * w1); + } + + public void update() + { + double inputPosition = this.inputPosition == null ? 0.0 : this.inputPosition.getDoubleValue(); + double inputVelocity = this.inputVelocity == null ? 0.0 : this.inputVelocity.getDoubleValue(); + double inputAcceleration = this.inputAcceleration == null ? 0.0 : this.inputAcceleration.getDoubleValue(); + + update(inputPosition, inputVelocity, inputAcceleration); + } + + public void update(double inputPosition) + { + update(inputPosition, 0.0, 0.0); + } + + public void update(double inputPosition, double inputVelocity) + { + update(inputPosition, inputVelocity, 0.0); + } + + public void update(double inputPosition, double inputVelocity, double inputAcceleration) + { + if (!hasBeenInitialized.getBooleanValue()) + initialize(inputPosition, inputVelocity, inputAcceleration); + + double positionError = inputPosition - this.getDoubleValue(); + double velocityError = inputVelocity - smoothedRate.getDoubleValue(); + double accelerationError = inputAcceleration - smoothedAcceleration.getDoubleValue(); + double jerk = accelerationGain.getDoubleValue() * accelerationError + velocityGain.getDoubleValue() * velocityError + positionGain.getDoubleValue() + * positionError; + jerk = MathTools.clamp(jerk, -maximumJerk.getDoubleValue(), maximumJerk.getDoubleValue()); + + smoothedJerk.set(jerk); + smoothedAcceleration.add(smoothedJerk.getDoubleValue() * dt); + smoothedAcceleration.set(MathTools.clamp(smoothedAcceleration.getDoubleValue(), maximumJerk.getDoubleValue())); + smoothedRate.add(smoothedAcceleration.getDoubleValue() * dt); + this.add(smoothedRate.getDoubleValue() * dt); + } + + public void initialize(double inputPosition, double inputVelocity, double inputAcceleration) + { + this.set(inputPosition); + smoothedRate.set(inputVelocity); + smoothedAcceleration.set(inputAcceleration); + smoothedJerk.set(0.0); + + this.hasBeenInitialized.set(true); + } + + public void reset() + { + this.hasBeenInitialized.set(false); + smoothedRate.set(0.0); + smoothedAcceleration.set(0.0); + smoothedJerk.set(0.0); + } + +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/MovingAverageYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/MovingAverageYoDouble.java new file mode 100644 index 00000000..bad8f2db --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/MovingAverageYoDouble.java @@ -0,0 +1,114 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * @author thutcheson + *

+ *

+ *

+ *

+ * A BetaFilteredYoVariable is a filtered version of an input YoVar. + * This is a moving average filter. + * Either a YoVariable holding the unfiltered val is passed in to the + * constructor and update() is called every tick, or update(double) is + * called every tick. The BetaFilteredYoVariable updates it's val + * with the current filtered version using + *

+ *
+ *            filtered_{n} = (raw_{0} + ... + raw_{n-1} + raw_{n}) / n
+ *         
+ */ +public class MovingAverageYoDouble extends YoDouble +{ + private int beta; + private int index = 0; + @SuppressWarnings("unused") + private final YoDouble betaVariable; + + private final YoDouble position; + + private final double[] raw; + private boolean bufferFull = false; + + private final YoBoolean hasBeenCalled; + + public MovingAverageYoDouble(String name, YoRegistry registry, int beta) + { + this(name, "", registry, beta, null); + } + + public MovingAverageYoDouble(String name, String description, YoRegistry registry, int beta) + { + this(name, description, registry, beta, null); + } + + public MovingAverageYoDouble(String name, YoRegistry registry, int beta, YoDouble positionVariable) + { + this(name, "", registry, beta, positionVariable); + } + + public MovingAverageYoDouble(String name, String description, YoRegistry registry, int beta, YoDouble positionVariable) + { + super(name, description, registry); + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + + this.beta = beta; + this.position = positionVariable; + this.betaVariable = null; + + raw = new double[beta]; + + reset(); + } + + public void reset() + { + hasBeenCalled.set(false); + bufferFull = false; + index = 0; + + for (int i = 0; i < beta; i++) + { + set(0.0); + } + } + + public void update() + { + if (position == null) + { + throw new NullPointerException("BetaFilteredYoVariable must be constructed with a non null " + + "position variable to call update(), otherwise use update(double)"); + } + + update(position.getDoubleValue()); + } + + public void update(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + set(currentPosition, false); + } + + raw[index++] = currentPosition; + if (index == beta) + { + index = 0; + bufferFull = true; + } + + final int size = bufferFull ? beta : index; + double value = 0.0; + for (int i = 0; i < size; i++) + { + value += raw[i]; + } + + set(value / size); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/ProcessingYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/ProcessingYoVariable.java new file mode 100644 index 00000000..b38e0214 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/ProcessingYoVariable.java @@ -0,0 +1,10 @@ +package us.ihmc.yoVariables.filters; + +public interface ProcessingYoVariable +{ + void update(); + + default void reset() + { + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/RateLimitedYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/RateLimitedYoVariable.java new file mode 100644 index 00000000..475a49ab --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/RateLimitedYoVariable.java @@ -0,0 +1,217 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * This is a yo variable whose rate of change is clamped to some maximum value. To use it, either create this variable passing in the non-limited yo variable to + * track and then call {@link #update()}, or unlink it keep it unlinked from a specific yo variable and call {@link #update(double)}. This variable when then + * track the specified variable or input, but will not change faster than the rate provided by {@code maxRateVariable}. + */ +public class RateLimitedYoVariable extends YoDouble +{ + private final DoubleProvider maxRateVariable; + + private final DoubleProvider unlimitedPosition; + private final YoBoolean limited; + + private final double dt; + + private final YoBoolean hasBeenCalled; + + /** + * Constructs this variable with no double to track. The value contained in this yo variable will track the desired reference, but will limit the maximum + * rate of change to {@code maxRate}. + *

+ * The maximum rate of change is enforced as a maximum step size every time {@link #update(double)} is called. The maximum step size + * can be calculated as {@code maxRate} * {@code dt}. + *

+ *

+ * To use this variable after using this constructor, you must call {@link #update(double)}. Calling {@link #update()} will result in a null pointer + * exception. + *

+ *

+ * A known edge case is if {@link #update(double)} is called more than once per control update. In this case, the maximum rate is enforced each time + * {@link #update()} called, rather than per each control update. Avoid doing this. + *

+ * + * @param name name of this variable. + * @param registry registry to add this variable to. + * @param maxRate maximum rate of change this value can experience every time {@link #update(double)} is called. + * @param dt expected time change since between calls of {@link #update(double)}. + */ + public RateLimitedYoVariable(String name, YoRegistry registry, double maxRate, double dt) + { + this(name, registry, maxRate, null, dt); + } + + /** + * Constructs this variable with no double to track. The value contained in this yo variable will track the desired reference, but will limit the maximum + * rate of change to {@code maxRateVariable}. + *

+ * The maximum rate of change is enforced as a maximum step size every time {@link #update(double)} is called. The maximum step size + * can be calculated as {@code maxRateVariable} * {@code dt}. + *

+ *

+ * To use this variable after using this constructor, you must call {@link #update(double)}. Calling {@link #update()} will result in a null pointer + * exception. + *

+ *

+ * A known edge case is if {@link #update(double)} is called more than once per control update. In this case, the maximum rate is enforced each time + * {@link #update()} called, rather than per each control update. Avoid doing this. + *

+ * + * @param name name of this variable. + * @param registry registry to add this variable to. + * @param maxRateVariable maximum rate of change this value can experience every time {@link #update(double)} is called. + * @param dt expected time change since between calls of {@link #update(double)}. + */ + public RateLimitedYoVariable(String name, YoRegistry registry, DoubleProvider maxRateVariable, double dt) + { + this(name, registry, maxRateVariable, null, dt); + } + + /** + * Constructs this variable to track {@code positionVariable}. The value contained in this yo variable will track the desired reference passed in at + * construction, but will limit the maximum + * rate of change to {@code maxRate}. + *

+ * The maximum rate of change is enforced as a maximum step size every time {@link #update()} is called. The maximum step size + * can be calculated as {@code maxRate} * {@code dt}. + *

+ *

+ * To use this variable after using this constructor, you should call {@link #update()}. Calling {@link #update(double)} will not track the variable + * provided + * by {@code positionVariable}. + *

+ *

+ * A known edge case is if {@link #update()} is called more than once per control update. In this case, the maximum rate is enforced each time + * {@link #update()} called, rather than per each control update. Avoid doing this. + *

+ * + * @param name name of this variable. + * @param registry registry to add this variable to. + * @param maxRate maximum rate of change this value can experience every time {@link #update()} is called. + * @param positionVariable varible provider that this variable will track. + * @param dt expected time change since between calls of {@link #update()}. + */ + public RateLimitedYoVariable(String name, YoRegistry registry, double maxRate, DoubleProvider positionVariable, double dt) + { + this(name, registry, VariableTools.createMaxRateYoDouble(name, "", maxRate, registry), positionVariable, dt); + } + + /** + * Constructs this variable to track {@code positionVariable}. The value contained in this yo variable will track the desired reference passed in at + * construction, but will limit the maximum + * rate of change to {@code maxRateVariable}. + *

+ * The maximum rate of change is enforced as a maximum step size every time {@link #update()} is called. The maximum step size + * can be calculated as {@code maxRateVariable} * {@code dt}. + *

+ *

+ * To use this variable after using this constructor, you should call {@link #update()}. Calling {@link #update(double)} will not track the variable + * provided + * by {@code positionVariable}. + *

+ *

+ * A known edge case is if {@link #update()} is called more than once per control update. In this case, the maximum rate is enforced each time + * {@link #update()} called, rather than per each control update. Avoid doing this. + *

+ * + * @param name name of this variable. + * @param registry registry to add this variable to. + * @param maxRateVariable maximum rate of change this value can experience every time {@link #update()} is called. + * @param unlimitedPosition varible provider that this variable will track. + * @param dt expected time change since between calls of {@link #update()}. + */ + public RateLimitedYoVariable(String name, YoRegistry registry, DoubleProvider maxRateVariable, DoubleProvider unlimitedPosition, double dt) + { + super(name, registry); + + this.hasBeenCalled = VariableTools.createHasBeenCalledYoBoolean(name, "", registry); + this.limited = VariableTools.createLimitedCalledYoBoolean(name, "", registry); + + this.unlimitedPosition = unlimitedPosition; + this.maxRateVariable = maxRateVariable; + + this.dt = dt; + + reset(); + } + + /** + * Resets this variable. On the next time {@link #update()} or {@link #update(double)} is called, it will automatically be set to the variable to track, + * rather than experiencing any rate limiting. + */ + public void reset() + { + hasBeenCalled.set(false); + } + + /** + * Updates the value contained in this yo variable to track the value contained in {@code unlimitedPosition}. If the {@code unlimitedPosition} can be + * achieved with a rate of change + * less that provided by {@code maxRateVariable}, then the value stored in this variable and returned by {@link #getValue()} will match + * {@code unlimitedPosition}. + * Otherwise, it will step towards the new position at maximum rate. This can be computed using the following pseudo-code: + *

+ *

  • error = currentPosition - getValue()
  • + *
  • if abs(error) < maxRate * dt
  • + *
  • set(currentPosition)
  • + *
  • else
  • + *
  • set(currentPosition + sign(error) * maxRate * dt)
  • + *

    + * + * @throws NullPointerException if this class was constructed with no {@code unlimitedPosition} variable to track. + */ + public void update() + { + if (unlimitedPosition == null) + { + throw new NullPointerException( + getClass().getSimpleName() + " must be constructed with a non null " + "position variable to call update(), otherwise use update(double)"); + } + + update(unlimitedPosition.getValue()); + } + + /** + * Updates the value contained in this yo variable to track {@code currentPosition}. If the {@code currentPosition} can be achieved with a rate of change + * less that provided by {@code maxRateVariable}, then the value stored in this variable and returned by {@link #getValue()} will match + * {@code currentPosition}. + * Otherwise, it will step towards the new position at maximum rate. This can be computed using the following pseudo-code: + *

    + *

  • error = currentPosition - getValue()
  • + *
  • if abs(error) < maxRate * dt
  • + *
  • set(currentPosition)
  • + *
  • else
  • + *
  • set(currentPosition + sign(error) * maxRate * dt)
  • + *

    + * + * @param currentPosition position to try and achieve within rate limits. + */ + public void update(double currentPosition) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + set(currentPosition); + } + + if (maxRateVariable.getValue() < 0) + throw new RuntimeException("The maxRate parameter in the RateLimitedYoVariable cannot be negative."); + + double difference = currentPosition - getDoubleValue(); + if (Math.abs(difference) > maxRateVariable.getValue() * dt) + { + difference = Math.signum(difference) * maxRateVariable.getValue() * dt; + this.limited.set(true); + } + else + this.limited.set(false); + + set(getDoubleValue() + difference); + } +} \ No newline at end of file diff --git a/src/filters/java/us/ihmc/yoVariables/filters/RunningAverageYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/RunningAverageYoDouble.java new file mode 100644 index 00000000..25c05db6 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/RunningAverageYoDouble.java @@ -0,0 +1,52 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoInteger; + +public class RunningAverageYoDouble extends YoDouble +{ + private final YoInteger sampleSize; + private final DoubleProvider dataSource; + + public RunningAverageYoDouble(String name, YoRegistry registry) + { + this(name, null, registry); + } + + public RunningAverageYoDouble(String name, DoubleProvider dataSource, YoRegistry registry) + { + super(name, registry); + + this.dataSource = dataSource; + sampleSize = new YoInteger(name + "SampleSize", registry); + } + + public void update() + { + if (dataSource == null) + { + throw new NullPointerException(getClass().getSimpleName() + " must be constructed with a non null " + + "dataSource variable to call update(), otherwise use update(double)"); + } + + update(dataSource.getValue()); + } + + public void update(double dataSource) + { + sampleSize.increment(); + add((dataSource - getValue()) / sampleSize.getValue()); + } + + public void reset() + { + sampleSize.set(0); + } + + public int getSampleSize() + { + return sampleSize.getValue(); + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDouble.java b/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDouble.java new file mode 100644 index 00000000..89cda9cb --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDouble.java @@ -0,0 +1,209 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; + +/** + * A discrete-time second order filter using the bilinear transform + *

    + * ---------------------------------------------------------------- + * LOW PASS: + *

    + * Y(s) omega^2 + * ---- = ---------------------------------- + * X(s) s^2 + 2 * xi * omega * s + omega^2 + *

    + * ---------------------------------------------------------------- + * NOTCH: + *

    + * Y(s) s^2 + omega^2 + * ---- = ---------------------------------- + * X(s) s^2 + 2 * xi * omega * s + omega^2 + *

    + * ---------------------------------------------------------------- + * HIGH PASS: + *

    + * Y(s) s^2 + * ---- = ---------------------------------- + * X(s) s^2 + 2 * xi * omega * s + omega^2 + *

    + * ----------------------------------------------------------------- + *

    + * omega = 2 * PI * naturalFrequencyInHz + * xi = dampingRatio + */ +public class SecondOrderFilteredYoDouble extends YoDouble implements ProcessingYoVariable +{ + private final double dt; + private final SecondOrderFilteredYoVariableParameters parameters; + protected final YoBoolean hasBeenCalled; + private final YoDouble inputVariable; + private final YoDouble[] input; + private final YoDouble[] output; + private final double a[]; + private final double b[]; + + public SecondOrderFilteredYoDouble(String name, YoRegistry registry, double dt, double naturalFrequencyInHz, double dampingRatio, SecondOrderFilterType filterType) + { + this(name, registry, dt, new SecondOrderFilteredYoVariableParameters(name, registry, naturalFrequencyInHz, dampingRatio, filterType), null); + } + + public SecondOrderFilteredYoDouble(String name, YoRegistry registry, double dt, SecondOrderFilteredYoVariableParameters parameters) + { + this(name, registry, dt, parameters, null); + } + + public SecondOrderFilteredYoDouble(String name, + YoRegistry registry, + double dt, + double naturalFrequencyInHz, + double dampingRatio, + SecondOrderFilterType filterType, + YoDouble inputVariable) + { + this(name, registry, dt, new SecondOrderFilteredYoVariableParameters(name, registry, naturalFrequencyInHz, dampingRatio, filterType), inputVariable); + } + + public SecondOrderFilteredYoDouble(String name, YoRegistry registry, double dt, SecondOrderFilteredYoVariableParameters parameters, YoDouble inputVariable) + { + super(name, registry); + this.dt = dt; + this.parameters = parameters; + this.hasBeenCalled = new YoBoolean(name + "HasBeenCalled", registry); + this.inputVariable = inputVariable; + this.input = new YoDouble[3]; + this.output = new YoDouble[3]; + this.a = new double[3]; + this.b = new double[3]; + for (int i = 0; i < 3; i++) + { + this.input[i] = new YoDouble(name + "input" + i, registry); + this.output[i] = new YoDouble(name + "output" + i, registry); + } + reset(); + } + + @Override + public void reset() + { + hasBeenCalled.set(false); + computeCoefficients(); + } + + @Override + public void update() + { + if (inputVariable == null) + { + throw new NullPointerException( + "SecondOrderFilteredYoVariable must be constructed with a non null position variable to call update(), otherwise use update(double)"); + } + + update(inputVariable.getDoubleValue()); + } + + public void update(double currentInputValue) + { + if (!hasBeenCalled.getBooleanValue()) + { + hasBeenCalled.set(true); + set(currentInputValue); + for (int i = 0; i < 3; i++) + { + input[i].set(currentInputValue); + output[i].set(currentInputValue); + } + return; + } + + for (int i = 2; i > 0; i--) + { + input[i].set(input[i - 1].getDoubleValue()); + output[i].set(output[i - 1].getDoubleValue()); + } + input[0].set(currentInputValue); + + double currentOutputValue = 0.0; + currentOutputValue += b[2] * input[2].getDoubleValue(); + currentOutputValue += b[1] * input[1].getDoubleValue(); + currentOutputValue += b[0] * input[0].getDoubleValue(); + currentOutputValue -= a[2] * output[2].getDoubleValue(); + currentOutputValue -= a[1] * output[1].getDoubleValue(); + currentOutputValue /= a[0]; + output[0].set(currentOutputValue); + + set(currentOutputValue); + } + + public void setNaturalFrequencyInHz(double naturalFrequencyInHz) + { + parameters.getNaturalFrequencyInHz().set(Math.min(Math.max(naturalFrequencyInHz, 0.0), 1.0 / (2.0 * dt))); + computeCoefficients(); + } + + public void setDampingRatio(double dampingRatio) + { + parameters.getDampingRatio().set(Math.max(dampingRatio, 0.0)); + computeCoefficients(); + } + + public boolean getHasBeenCalled() + { + return hasBeenCalled.getBooleanValue(); + } + + public void getFilterCoefficients(double[] b, double[] a) + { + if (b.length < 3) + throw new RuntimeException("b must be of length 3 or greater"); + + if (a.length < 3) + throw new RuntimeException("a must be of length 3 or greater"); + + for (int i = 0; i < 3; i++) + b[i] = this.b[i]; + for (int i = 3; i < b.length; i++) + b[i] = 0.0; + for (int i = 0; i < 3; i++) + a[i] = this.a[i]; + for (int i = 3; i < a.length; i++) + a[i] = 0.0; + } + + private void computeCoefficients() + { + double omega = 2 * Math.PI * parameters.getNaturalFrequencyInHz().getDoubleValue(); + double xi = parameters.getDampingRatio().getDoubleValue(); + + switch (parameters.getFilterType()) + { + case LOW_PASS: + b[0] = omega * omega; + b[1] = 2.0 * omega * omega; + b[2] = omega * omega; + break; + case NOTCH: + b[0] = 4.0 / (dt * dt) + omega * omega; + b[1] = 2.0 * omega * omega - 8.0 / (dt * dt); + b[2] = 4.0 / (dt * dt) + omega * omega; + break; + case HIGH_PASS: + b[0] = 4.0 / (dt * dt); + b[1] = -8.0 / (dt * dt); + b[2] = 4.0 / (dt * dt); + break; + case BAND: + throw new IllegalArgumentException("Band pass filters are not established for the second order filter yo variable."); + } + + a[0] = 4.0 / (dt * dt) + 4.0 / dt * xi * omega + omega * omega; + a[1] = 2.0 * omega * omega - 8.0 / (dt * dt); + a[2] = 4.0 / (dt * dt) - 4.0 / dt * xi * omega + omega * omega; + } + + public enum SecondOrderFilterType + { + LOW_PASS, NOTCH, BAND, HIGH_PASS + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoVariableParameters.java b/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoVariableParameters.java new file mode 100644 index 00000000..b7ce88ad --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoVariableParameters.java @@ -0,0 +1,36 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +public class SecondOrderFilteredYoVariableParameters +{ + private final YoDouble naturalFrequencyInHz; + private final YoDouble dampingRatio; + private final SecondOrderFilteredYoDouble.SecondOrderFilterType filterType; + + public SecondOrderFilteredYoVariableParameters(String name, YoRegistry registry, double naturalFrequencyInHz, double dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType filterType) + { + this.naturalFrequencyInHz = new YoDouble(name + "NaturalFrequency", registry); + this.naturalFrequencyInHz.set(naturalFrequencyInHz); + this.dampingRatio = new YoDouble(name + "DampingRatio", registry); + this.dampingRatio.set(dampingRatio); + this.filterType = filterType; + } + + public YoDouble getNaturalFrequencyInHz() + { + return naturalFrequencyInHz; + } + + public YoDouble getDampingRatio() + { + return dampingRatio; + } + + public SecondOrderFilteredYoDouble.SecondOrderFilterType getFilterType() + { + return filterType; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariable.java b/src/filters/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariable.java new file mode 100644 index 00000000..b6a0b8f0 --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariable.java @@ -0,0 +1,88 @@ +package us.ihmc.yoVariables.filters; + +import org.ejml.data.DMatrixRMaj; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoInteger; + +import java.util.Arrays; + +/** + * Filter the given yoVariable using a moving average filter. This class is NOT REWINDABLE! + */ +public class SimpleMovingAverageFilteredYoVariable extends YoDouble +{ + private final YoInteger windowSize; + private final YoDouble yoVariableToFilter; + + private final DMatrixRMaj previousUpdateValues = new DMatrixRMaj(0, 0); + private int bufferPosition = 0; + + private boolean bufferHasBeenFilled = false; + + public SimpleMovingAverageFilteredYoVariable(String name, int windowSize, YoRegistry registry) + { + this(name, windowSize, null, registry); + } + + public SimpleMovingAverageFilteredYoVariable(String name, int windowSize, YoDouble yoVariableToFilter, YoRegistry registry) + { + super(name, registry); + + this.yoVariableToFilter = yoVariableToFilter; + this.windowSize = new YoInteger(name + "WindowSize", registry); + this.windowSize.set(windowSize); + + previousUpdateValues.reshape(windowSize, 1); + Arrays.fill(previousUpdateValues.data, 0.0); + } + + public void setWindowSize(int windowSize) + { + this.windowSize.set(windowSize); + reset(); + } + + public void update() + { + update(yoVariableToFilter.getDoubleValue()); + } + + public void update(double value) + { + if (previousUpdateValues.getNumRows() != windowSize.getIntegerValue()) + { + reset(); + } + previousUpdateValues.set(bufferPosition, 0, value); + + bufferPosition++; + + if (bufferPosition >= windowSize.getIntegerValue()) + { + bufferPosition = 0; + bufferHasBeenFilled = true; + } + + double average = 0; + for (int i = 0; i < windowSize.getIntegerValue(); i++) + { + average += previousUpdateValues.get(i, 0); + } + + this.set(average / ((double) windowSize.getIntegerValue())); + } + + public void reset() + { + bufferPosition = 0; + bufferHasBeenFilled = false; + previousUpdateValues.reshape(windowSize.getIntegerValue(), 1); + } + + public boolean getHasBufferWindowFilled() + { + return bufferHasBeenFilled; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/VariableTools.java b/src/filters/java/us/ihmc/yoVariables/filters/VariableTools.java new file mode 100644 index 00000000..d798ac1a --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/VariableTools.java @@ -0,0 +1,48 @@ +package us.ihmc.yoVariables.filters; + +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoInteger; + +public class VariableTools +{ + public static YoBoolean createHasBeenCalledYoBoolean(String namePrefix, String nameSuffix, YoRegistry registry) + { + return new YoBoolean(namePrefix + "HasBeenCalled" + nameSuffix, registry); + } + + public static YoBoolean createLimitedCalledYoBoolean(String namePrefix, String nameSuffix, YoRegistry registry) + { + return new YoBoolean(namePrefix + "Limited" + nameSuffix, registry); + } + + public static DoubleProvider createMaxRateYoDouble(String namePrefix, String nameSuffix, double initialValue, YoRegistry registry) + { + YoDouble maxRate = new YoDouble(namePrefix + "MaxRate" + nameSuffix, registry); + maxRate.set(initialValue); + return maxRate; + } + + public static DoubleProvider createAlphaYoDouble(String namePrefix, String nameSuffix, double initialValue, YoRegistry registry) + { + YoDouble maxRate = new YoDouble(namePrefix + "AlphaVariable" + nameSuffix, registry); + maxRate.set(initialValue); + return maxRate; + } + + public static YoInteger createWindowSizeYoInteger(String namePrefix, String nameSuffix, int initialValue, YoRegistry registry) + { + YoInteger windowSize = new YoInteger(namePrefix + "WindowSize" + nameSuffix, registry); + windowSize.set(initialValue); + return windowSize; + } + + public static DoubleProvider createMaxAccelerationYoDouble(String namePrefix, String nameSuffix, double initialValue, YoRegistry registry) + { + YoDouble maxRate = new YoDouble(namePrefix + "MaxAcceleration" + nameSuffix, registry); + maxRate.set(initialValue); + return maxRate; + } +} diff --git a/src/filters/java/us/ihmc/yoVariables/filters/YoMatrix.java b/src/filters/java/us/ihmc/yoVariables/filters/YoMatrix.java new file mode 100644 index 00000000..e640d87b --- /dev/null +++ b/src/filters/java/us/ihmc/yoVariables/filters/YoMatrix.java @@ -0,0 +1,352 @@ +package us.ihmc.yoVariables.filters; + +import org.ejml.data.*; +import org.ejml.ops.MatrixIO; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; +import us.ihmc.yoVariables.variable.YoInteger; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +/** + * YoMatrix. Object for holding a matrix of YoVariables so that Matrices can be rewound. Has a + * maximum number of rows and columns and an actual number of rows and columns. If you set with a + * smaller matrix, then the actual size will be the size of the passed in matrix. extra entries will + * be set to NaN. If you get the contents the matrix you pack must be the correct size. + * + * @author JerryPratt + */ +public class YoMatrix implements DMatrix, ReshapeMatrix +{ + // TODO: eventually consolidate YoMatrix implementations + + private static final long serialVersionUID = 2156411740647948028L; + + private final int maxNumberOfRows, maxNumberOfColumns; + + private final YoInteger numberOfRows, numberOfColumns; + private final YoDouble[][] variables; + + public YoMatrix(String name, int maxNumberOfRows, int maxNumberOfColumns, YoRegistry registry) + { + this(name, null, maxNumberOfRows, maxNumberOfColumns, null, null, registry); + } + + public YoMatrix(String name, int maxNumberOfRows, int maxNumberOfColumns, String[] rowNames, YoRegistry registry) + { + this(name, null, maxNumberOfRows, maxNumberOfColumns, rowNames, null, registry); + } + + public YoMatrix(String name, int maxNumberOfRows, int maxNumberOfColumns, String[] rowNames, String[] columnNames, YoRegistry registry) + { + this(name, null, maxNumberOfRows, maxNumberOfColumns, rowNames, columnNames, registry); + } + + public YoMatrix(String name, String description, int maxNumberOfRows, int maxNumberOfColumns, YoRegistry registry) + { + this(name, description, maxNumberOfRows, maxNumberOfColumns, null, null, registry); + } + + public YoMatrix(String name, String description, int maxNumberOfRows, int maxNumberOfColumns, String[] rowNames, YoRegistry registry) + { + this(name, description, maxNumberOfRows, maxNumberOfColumns, rowNames, null, registry); + } + + public YoMatrix(String name, String description, int maxNumberOfRows, int maxNumberOfColumns, String[] rowNames, String[] columnNames, YoRegistry registry) + { + this.maxNumberOfRows = maxNumberOfRows; + this.maxNumberOfColumns = maxNumberOfColumns; + + this.numberOfRows = new YoInteger(name + "NumRows", registry); + this.numberOfColumns = new YoInteger(name + "NumCols", registry); + + this.numberOfRows.set(maxNumberOfRows); + this.numberOfColumns.set(maxNumberOfColumns); + + variables = new YoDouble[maxNumberOfRows][maxNumberOfColumns]; + + for (int row = 0; row < maxNumberOfRows; row++) + { + for (int column = 0; column < maxNumberOfColumns; column++) + { + switch (checkNames(rowNames, columnNames)) + { + case NONE: + { + variables[row][column] = new YoDouble(getFieldName(name, row, column), description, registry); // names are simply the row and column indices + variables[row][column].setToNaN(); + break; + } + case ROWS: + { + if (maxNumberOfColumns > 1) + throw new IllegalArgumentException( + "The YoMatrix must be a column vector if only row names are provided, else unique names cannot be generated."); + + variables[row][column] = new YoDouble(getFieldName(name, rowNames[row], ""), description, registry); // names are the row names, no column identifier + variables[row][column].setToNaN(); + break; + } + case ROWS_AND_COLUMNS: + { + variables[row][column] = new YoDouble(getFieldName(name, rowNames[row], columnNames[column]), description, registry); // names are the row and column names + variables[row][column].setToNaN(); + break; + } + } + } + } + } + + public static String getFieldName(String prefix, int row, int column) + { + return getFieldName(prefix, "_" + row, "_" + column); + } + + public static String getFieldName(String prefix, String rowName, String columName) + { + return prefix + rowName + columName; + } + + private enum NamesProvided + { + NONE, ROWS, ROWS_AND_COLUMNS + } + + private NamesProvided checkNames(String[] rowNames, String[] columnNames) + { + if (rowNames == null && columnNames == null) + return NamesProvided.NONE; + else if (rowNames != null && columnNames == null) + return NamesProvided.ROWS; + else + return NamesProvided.ROWS_AND_COLUMNS; + } + + @Override + public double get(int row, int col) + { + if (col < 0 || col >= getNumCols() || row < 0 || row >= getNumRows()) + throw new IllegalArgumentException("Specified element is out of bounds: (" + row + " , " + col + ")"); + return unsafe_get(row, col); + } + + @Override + public double unsafe_get(int row, int col) + { + return variables[row][col].getValue(); + } + + @Override + public void set(int row, int col, double val) + { + if (col < 0 || col >= getNumCols() || row < 0 || row >= getNumRows()) + throw new IllegalArgumentException("Specified element is out of bounds: (" + row + " , " + col + ")"); + unsafe_set(row, col, val); + } + + @Override + public void unsafe_set(int row, int col, double val) + { + unsafe_set(row, col, val, true); + } + + private void unsafe_set(int row, int col, double val, boolean notifyListeners) + { + variables[row][col].set(val, notifyListeners); + } + + @Override + public int getNumElements() + { + return numberOfRows.getValue() * numberOfColumns.getValue(); + } + + @Override + public int getNumRows() + { + return numberOfRows.getValue(); + } + + @Override + public int getNumCols() + { + return numberOfColumns.getValue(); + } + + @Override + public void zero() + { + for (int row = 0; row < getNumRows(); row++) + { + for (int col = 0; col < getNumCols(); col++) + { + variables[row][col].set(0.0); + } + } + } + + @Override + public T copy() + { + throw new UnsupportedOperationException(); + } + + @Override + public T createLike() + { + throw new UnsupportedOperationException(); + } + + @Override + public T create(int numRows, int numCols) + { + throw new UnsupportedOperationException(); + } + + @Override + public void set(Matrix original) + { + if (original instanceof DMatrix otherMatrix) + { + reshape(otherMatrix.getNumRows(), otherMatrix.getNumCols()); + for (int row = 0; row < getNumRows(); row++) + { + for (int col = 0; col < getNumCols(); col++) + { + unsafe_set(row, col, otherMatrix.unsafe_get(row, col), false); + } + } + } + } + + @Override + public void print() + { + MatrixIO.printFancy(System.out, this, MatrixIO.DEFAULT_LENGTH); + } + + @Override + public void print(String format) + { + MatrixIO.print(System.out, this, format); + } + + @Override + public MatrixType getType() + { + return MatrixType.UNSPECIFIED; + } + + @Override + public void reshape(int numRows, int numCols) + { + if (numRows > maxNumberOfRows) + throw new IllegalArgumentException("Too many rows. Expected less or equal to " + maxNumberOfRows + ", was " + numRows); + else if (numCols > maxNumberOfColumns) + throw new IllegalArgumentException("Too many columns. Expected less or equal to " + maxNumberOfColumns + ", was " + numCols); + else if (numRows < 0 || numCols < 0) + throw new IllegalArgumentException("Cannot reshape with a negative number of rows or columns."); + + numberOfRows.set(numRows); + numberOfColumns.set(numCols); + + for (int row = 0; row < numRows; row++) + { + for (int col = numCols; col < maxNumberOfColumns; col++) + { + unsafe_set(row, col, Double.NaN, false); + } + } + + for (int row = numRows; row < maxNumberOfRows; row++) + { + for (int col = 0; col < maxNumberOfColumns; col++) + { + unsafe_set(row, col, Double.NaN, false); + } + } + } + + public void set(DMatrix matrix) + { + int numRows = matrix.getNumRows(); + int numCols = matrix.getNumCols(); + + if (((numRows > maxNumberOfRows) || (numCols > maxNumberOfColumns)) && (numRows > 0) && (numCols > 0)) + throw new RuntimeException("Not enough rows or columns. matrix to set is " + matrix.getNumRows() + " by " + matrix.getNumCols()); + + this.numberOfRows.set(numRows); + this.numberOfColumns.set(numCols); + + for (int row = 0; row < maxNumberOfRows; row++) + { + for (int column = 0; column < maxNumberOfColumns; column++) + { + double value; + if ((row < numRows) && (column < numCols)) + { + value = matrix.unsafe_get(row, column); + } + else + { + value = Double.NaN; + } + unsafe_set(row, column, value, false); + + } + } + } + + public void getAndReshape(DMatrixRMaj matrixToPack) + { + matrixToPack.reshape(getNumRows(), getNumCols()); + get(matrixToPack); + } + + public void get(DMatrix matrixToPack) + { + int numRows = matrixToPack.getNumRows(); + int numCols = matrixToPack.getNumCols(); + + if (((numRows > maxNumberOfRows) || (numCols > maxNumberOfColumns)) && (numRows > 0) && (numCols > 0)) + throw new RuntimeException("Not enough rows or columns. matrixToPack is " + matrixToPack.getNumRows() + " by " + matrixToPack.getNumCols()); + if ((numRows != this.numberOfRows.getIntegerValue()) || (numCols != this.numberOfColumns.getIntegerValue())) + throw new RuntimeException("Numer of rows and columns must be the same. Call getAndReshape() if you want to reshape the matrixToPack"); + + for (int row = 0; row < numRows; row++) + { + for (int column = 0; column < numCols; column++) + { + matrixToPack.unsafe_set(row, column, variables[row][column].getDoubleValue()); + } + } + } + + public void setToNaN(int numberOfRows, int numberOfColumns) + { + reshape(numberOfRows, numberOfColumns); + for (int row = 0; row < numberOfRows; row++) + { + for (int col = 0; col < numberOfColumns; col++) + { + unsafe_set(row, col, Double.NaN, false); + } + } + } + + public YoDouble getYoDouble(int row, int col) + { + return variables[row][col]; + } + + @Override + public String toString() + { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + MatrixIO.print(new PrintStream(stream), this); + + return stream.toString(); + } +} diff --git a/src/main/java/us/ihmc/yoVariables/math/YoMatrix.java b/src/main/java/us/ihmc/yoVariables/math/YoMatrix.java index 1c599c94..47a3599b 100644 --- a/src/main/java/us/ihmc/yoVariables/math/YoMatrix.java +++ b/src/main/java/us/ihmc/yoVariables/math/YoMatrix.java @@ -144,14 +144,38 @@ public YoMatrix(String name, String description, int maxNumberOfRows, int maxNum { switch (checkNames(rowNames, columnNames)) { - case NONE -> variables[row][column] = new YoDouble(name + "_" + row + "_" + column, description, registry); - case ROWS -> variables[row][column] = new YoDouble(name + rowNames[row], description, registry); - case ROWS_AND_COLUMNS -> variables[row][column] = new YoDouble(name + rowNames[row] + columnNames[column], description, registry); + case NONE -> variables[row][column] = new YoDouble(getFieldName(name, row, column), description, registry); + case ROWS -> variables[row][column] = new YoDouble(getFieldName(name, rowNames[row], ""), description, registry); + case ROWS_AND_COLUMNS -> variables[row][column] = new YoDouble(getFieldName(name, rowNames[row], columnNames[column]), description, registry); } } } } + /** + * Convenience provider for getting the field name of an individual cell in the matrix. This can be used if retrieving the variable name from a registry. + * @param prefix name of the entire matrix + * @param row row in question + * @param column column in question + * @return String describing the name. + */ + public static String getFieldName(String prefix, int row, int column) + { + return getFieldName(prefix, "_" + row, "_" + column); + } + + /** + * Convenience provider for getting the field name of an individual cell in the matrix. This can be used if retrieving the variable name from a registry. + * @param prefix name of the entire matrix + * @param rowName row name in question + * @param columnName column name in question + * @return String describing the name. + */ + public static String getFieldName(String prefix, String rowName, String columnName) + { + return prefix + rowName + columnName; + } + /** * Enum used to determine what names have been provided to the YoMatrix. */ @@ -212,7 +236,7 @@ public void scale(double scale) { for (int col = 0; col < getNumCols(); col++) { - unsafe_set(row, col, unsafe_get(row, col) * scale); + unsafe_set(row, col, unsafe_get(row, col) * scale, false); } } } @@ -233,7 +257,7 @@ public void scale(double scale, DMatrix matrix) { for (int col = 0; col < getNumCols(); col++) { - unsafe_set(row, col, matrix.unsafe_get(row, col) * scale); + unsafe_set(row, col, matrix.unsafe_get(row, col) * scale, false); } } } @@ -283,7 +307,7 @@ public void add(double alpha, DMatrix a, double beta, DMatrix b) { for (int col = 0; col < getNumCols(); col++) { - unsafe_set(row, col, alpha * a.unsafe_get(row, col) + beta * b.unsafe_get(row, col)); + unsafe_set(row, col, alpha * a.unsafe_get(row, col) + beta * b.unsafe_get(row, col), false); } } } @@ -314,7 +338,7 @@ public void addEquals(double alpha, DMatrix a) { for (int col = 0; col < getNumCols(); col++) { - unsafe_set(row, col, unsafe_get(row, col) + alpha * a.unsafe_get(row, col)); + unsafe_set(row, col, unsafe_get(row, col) + alpha * a.unsafe_get(row, col), false); } } } @@ -398,7 +422,7 @@ else if (numRows < 0 || numCols < 0) { for (int col = numCols; col < maxNumberOfColumns; col++) { - unsafe_set(row, col, Double.NaN); + unsafe_set(row, col, Double.NaN, false); } } @@ -406,7 +430,7 @@ else if (numRows < 0 || numCols < 0) { for (int col = 0; col < maxNumberOfColumns; col++) { - unsafe_set(row, col, Double.NaN); + unsafe_set(row, col, Double.NaN, false); } } } @@ -426,10 +450,30 @@ public void set(int row, int col, double val) unsafe_set(row, col, val); } + /** + * Sets the variable contained at the sepcified index, but does not check whether the field is in bounds. This will call any attached yo variable listeners. + * If you want to avoid calling these listeners, please call {@link #unsafe_set(int, int, double, boolean)}. + * @param row Matrix element's row index. + * @param col Matrix element's column index. + * @param val The element's new value. + */ @Override public void unsafe_set(int row, int col, double val) { - variables[row][col].set(val); + unsafe_set(row, col, val, true); + } + + /** + * Sets the variable contained at the sepcified index, but does not check whether the field is in bounds. Optionally allows you to avoid notifying + * listeners. + * @param row Matrix element's row index. + * @param col Matrix element's column index. + * @param val The element's new value. + * @param notifyListeners trigger for whether to notify attached yo variable listeners. + */ + public void unsafe_set(int row, int col, double val, boolean notifyListeners) + { + variables[row][col].set(val, notifyListeners); } /** @@ -450,7 +494,7 @@ public void set(Matrix original) { for (int col = 0; col < getNumCols(); col++) { - unsafe_set(row, col, otherMatrix.unsafe_get(row, col)); + unsafe_set(row, col, otherMatrix.unsafe_get(row, col), false); } } } @@ -473,7 +517,7 @@ public void setToNaN(int numRows, int numCols) { for (int col = 0; col < numCols; col++) { - unsafe_set(row, col, Double.NaN); + unsafe_set(row, col, Double.NaN, false); } } } @@ -507,9 +551,9 @@ public void zero() for (int col = 0; col < maxNumberOfColumns; col++) { if (row < getNumRows() && col < getNumCols()) - unsafe_set(row, col, 0.0); + unsafe_set(row, col, 0.0, false); else - unsafe_set(row, col, Double.NaN); + unsafe_set(row, col, Double.NaN, false); } } } diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransformTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransformTest.java new file mode 100644 index 00000000..91280c6d --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredRigidBodyTransformTest.java @@ -0,0 +1,152 @@ +package us.ihmc.yoVariables.euclid.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.transform.RigidBodyTransform; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.yawPitchRoll.YawPitchRoll; + +import java.util.Random; + +public class AlphaFilteredRigidBodyTransformTest +{ + private static final double EPSILON = 1.0e-15; + + @Test + public void testRegression() + { + Random random = new Random(3453456); + + double alpha = random.nextDouble(); + + AlphaFilteredRigidBodyTransform filteredRigidBodyTransform = new AlphaFilteredRigidBodyTransform(); + filteredRigidBodyTransform.setAlpha(0.9); + + RigidBodyTransform unfilteredRigidBodyTransform = new RigidBodyTransform(); + + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(0.1, 0.1, 0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, 0.1, 0.1)); + + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.9900332889206207 -0.0894177463594257 0.1088051169064211 | 0.1000000000000000 + 0.0993346653975306 0.9910282997404070 -0.0894177463594257 | 0.1000000000000000 + -0.0998334166468282 0.0993346653975306 0.9900332889206207 | 0.1000000000000000 + 0.0000000000000000 0.0000000000000000 0.0000000000000000 | 1.0000000000000000 + """), filteredRigidBodyTransform, EPSILON); + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(0.1, 0.1, 0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, 0.1, 0.1)); + + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.9879463963435010 -0.0977116814683914 0.1200597570233632 | 0.1100000000000000 + 0.1097050673132180 0.9891497487794362 -0.0977116814683914 | 0.1100000000000000 + -0.1092095058027995 0.1097050673132180 0.9879463963435011 | 0.1100000000000000 + 0.0000000000000000 0.0000000000000000 0.0000000000000000 | 1.0000000000000000 + """), filteredRigidBodyTransform, EPSILON); + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(0.1, 0.1, 0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, 0.1, 0.1)); + + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.9834409873207434 -0.1131163390455875 0.1415927904185955 | 0.1290000000000000 + 0.1295926256343390 0.9850941301328119 -0.1131163390455875 | 0.1290000000000000 + -0.1266869205514076 0.1295926256343390 0.9834409873207435 | 0.1290000000000000 + 0.0000000000000000 0.0000000000000000 0.0000000000000000 | 1.0000000000000000 + """), filteredRigidBodyTransform, EPSILON); + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(0.1, 0.1, 0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, 0.1, 0.1)); + + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.9757980801204758 -0.1342587634073139 0.1726055945835227 | 0.1561000000000000 + 0.1583397744951649 0.9782142404714618 -0.1342587634073140 | 0.1561000000000000 + -0.1508198350549846 0.1583397744951649 0.9757980801204760 | 0.1561000000000000 + 0.0000000000000000 0.0000000000000000 0.0000000000000000 | 1.0000000000000000 + """), filteredRigidBodyTransform, EPSILON); + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(0.1, 0.1, 0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, 0.1, 0.1)); + + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.9640634283712346 -0.1596208716326060 0.2123743944460991 | 0.1904900000000000 + 0.1953779100890390 0.9676510990995081 -0.1596208716326060 | 0.1904900000000000 + -0.1800254935456074 0.1953779100890391 0.9640634283712346 | 0.1904900000000000 + 0.0000000000000000 0.0000000000000000 0.0000000000000000 | 1.0000000000000000 + """), filteredRigidBodyTransform, EPSILON); + } + + @Test + public void testReset() + { + Random random = new Random(3453456); + + double alpha = random.nextDouble(); + + AlphaFilteredRigidBodyTransform filteredRigidBodyTransform = new AlphaFilteredRigidBodyTransform(); + filteredRigidBodyTransform.setAlpha(0.9); + + RigidBodyTransform unfilteredRigidBodyTransform = new RigidBodyTransform(); + + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(-0.1, 0.1, -0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, -0.1, 0.1)); + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.990033288920620700 -0.109251584435635540 -0.088871694747662740 | -0.100000000000000000 + 0.099334665397530610 0.989038278100834400 -0.109251584435635540 | 0.100000000000000000 + 0.099833416646828150 0.099334665397530610 0.990033288920620700 | -0.100000000000000000 + 0.000000000000000000 0.000000000000000000 0.000000000000000000 | 1.000000000000000000 + """), filteredRigidBodyTransform, EPSILON); + + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(-0.1, 0.1, -0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, -0.1, 0.1)); + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.987946818678216400 -0.120594329665051940 -0.097046850109766770 | -0.110000000000000010 + 0.108601364045026250 0.986743508405399000 -0.120594329665051990 | 0.110000000000000010 + 0.110303341704367470 0.108601364045026240 0.987946818678216500 | -0.110000000000000010 + 0.000000000000000000 0.000000000000000000 0.000000000000000000 | 1.000000000000000000 + """), filteredRigidBodyTransform, EPSILON); + + filteredRigidBodyTransform.reset(); + + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(-0.1, 0.1, -0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, -0.1, 0.1)); + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.912137624808415300 -0.343802033741961170 -0.223170596189895150 | -0.300000000000000040 + 0.256378604455114070 0.903366023698333700 -0.343802033741961170 | 0.300000000000000040 + 0.319804572491560860 0.256378604455114070 0.912137624808415400 | -0.300000000000000040 + 0.000000000000000000 0.000000000000000000 0.000000000000000000 | 1.000000000000000000 + """), filteredRigidBodyTransform, EPSILON); + + unfilteredRigidBodyTransform.getTranslation().add(new Point3D(-0.1, 0.1, -0.1)); + unfilteredRigidBodyTransform.getRotation().append(new YawPitchRoll(0.1, -0.1, 0.1)); + filteredRigidBodyTransform.update(unfilteredRigidBodyTransform); + System.out.println(EuclidCoreTestMissingTools.toStringFullPrecision(filteredRigidBodyTransform)); + EuclidCoreTestTools.assertEquals(EuclidCoreTestMissingTools.newRigidBodyTransformFromString(""" + 0.906330827001449200 -0.355699718189048300 -0.228127469865204740 | -0.310000000000000050 + 0.262498500897342700 0.896979513426520900 -0.355699718189048330 | 0.310000000000000050 + 0.331147956438683330 0.262498500897342700 0.906330827001449300 | -0.310000000000000050 + 0.000000000000000000 0.000000000000000000 0.000000000000000000 | 1.000000000000000000 + """), filteredRigidBodyTransform, EPSILON); + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3DTest.java new file mode 100644 index 00000000..80aab55a --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredTuple3DTest.java @@ -0,0 +1,106 @@ +package us.ihmc.yoVariables.euclid.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.commons.Epsilons; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.Tuple3DBasicsTest; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlphaFilteredTuple3DTest extends Tuple3DBasicsTest +{ + private double alpha; + + @Test + public void testFirstSet() + { + double alpha = 0.0; + AlphaFilteredTuple3D tuple = new AlphaFilteredTuple3D(() -> alpha); + tuple.set(0.0, 1.0, 2.0); + + assertEquals(0.0, tuple.getX(), getEpsilon()); + assertEquals(1.0, tuple.getY(), getEpsilon()); + assertEquals(2.0, tuple.getZ(), getEpsilon()); + } + + @Test + public void testFilteredSetters() + { + Random random = new Random(12951L); + for (int i = 0; i < 1000; ++i) + { + alpha = random.nextDouble(0.0, 1.0); + AlphaFilteredTuple3D tuple = createRandomTuple(random); + + double originalX = tuple.getX(); + double originalY = tuple.getY(); + double originalZ = tuple.getZ(); + + Point3D point = new Point3D(createRandomTuple(random)); + + tuple.setX(point.getX()); + tuple.setY(point.getY()); + tuple.setZ(point.getZ()); + + double expectedX = (1.0 - alpha) * point.getX() + alpha * originalX; + double expectedY = (1.0 - alpha) * point.getY() + alpha * originalY; + double expectedZ = (1.0 - alpha) * point.getZ() + alpha * originalZ; + + assertEquals(expectedX, tuple.getX(), getEpsilon()); + assertEquals(expectedY, tuple.getY(), getEpsilon()); + assertEquals(expectedZ, tuple.getZ(), getEpsilon()); + } + } + + @Test + public void testSetOther() + { + Random random = new Random(621541L); + for (int i = 0; i < 1000; ++i) + { + alpha = random.nextDouble(0.0, 1.0); + AlphaFilteredTuple3D tuple = createRandomTuple(random); + + double originalX = tuple.getX(); + double originalY = tuple.getY(); + double originalZ = tuple.getZ(); + + Point3D point = new Point3D(createRandomTuple(random)); + tuple.set(point); + + double expectedX = (1.0 - alpha) * point.getX() + alpha * originalX; + double expectedY = (1.0 - alpha) * point.getY() + alpha * originalY; + double expectedZ = (1.0 - alpha) * point.getZ() + alpha * originalZ; + + assertEquals(expectedX, tuple.getX(), getEpsilon()); + assertEquals(expectedY, tuple.getY(), getEpsilon()); + assertEquals(expectedZ, tuple.getZ(), getEpsilon()); + } + } + + @Override + public AlphaFilteredTuple3D createEmptyTuple() + { + return new AlphaFilteredTuple3D(() -> alpha); + } + + @Override + public AlphaFilteredTuple3D createTuple(double v, double v1, double v2) + { + return new AlphaFilteredTuple3D(v, v1, v2, () -> alpha); + } + + @Override + public AlphaFilteredTuple3D createRandomTuple(Random random) + { + return new AlphaFilteredTuple3D(random.nextDouble(), random.nextDouble(), random.nextDouble(), () -> alpha); + } + + @Override + public double getEpsilon() + { + return Epsilons.ONE_TEN_MILLIONTH; + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2DTest.java new file mode 100644 index 00000000..eb861ee5 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint2DTest.java @@ -0,0 +1,54 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple2D.Point2D; +import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class AlphaFilteredYoFramePoint2DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + AlphaFilteredYoFramePoint2D filteredPoint = new AlphaFilteredYoFramePoint2D("tested", "", registry, alpha, ReferenceFrame.getWorldFrame()); + AlphaFilteredYoVariable xFiltered = new AlphaFilteredYoVariable("xRef", registry, alpha); + AlphaFilteredYoVariable yFiltered = new AlphaFilteredYoVariable("yRef", registry, alpha); + + Point2D unfilteredPoint = new Point2D(); + + for (int j = 0; j < 10; j++) + { + unfilteredPoint.add(EuclidCoreRandomTools.nextPoint2D(random, 0.0, 0.5)); + + filteredPoint.update(unfilteredPoint); + xFiltered.update(unfilteredPoint.getX()); + yFiltered.update(unfilteredPoint.getY()); + + EuclidCoreTestTools.assertEquals(new Point2D(xFiltered.getValue(), yFiltered.getValue()), filteredPoint, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3DTest.java new file mode 100644 index 00000000..abbe1fa5 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFramePoint3DTest.java @@ -0,0 +1,56 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class AlphaFilteredYoFramePoint3DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + AlphaFilteredYoFramePoint3D filteredPoint = new AlphaFilteredYoFramePoint3D("tested", "", registry, alpha, ReferenceFrame.getWorldFrame()); + AlphaFilteredYoVariable xFiltered = new AlphaFilteredYoVariable("xRef", registry, alpha); + AlphaFilteredYoVariable yFiltered = new AlphaFilteredYoVariable("yRef", registry, alpha); + AlphaFilteredYoVariable zFiltered = new AlphaFilteredYoVariable("zRef", registry, alpha); + + Point3D unfilteredPoint = new Point3D(); + + for (int j = 0; j < 10; j++) + { + unfilteredPoint.add(EuclidCoreRandomTools.nextPoint3D(random, 0.0, 0.5)); + + filteredPoint.update(unfilteredPoint); + xFiltered.update(unfilteredPoint.getX()); + yFiltered.update(unfilteredPoint.getY()); + zFiltered.update(unfilteredPoint.getZ()); + + EuclidCoreTestTools.assertEquals(new Point3D(xFiltered.getValue(), yFiltered.getValue(), zFiltered.getValue()), filteredPoint, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternionTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternionTest.java new file mode 100644 index 00000000..009a3b90 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameQuaternionTest.java @@ -0,0 +1,158 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.apache.commons.lang3.mutable.MutableDouble; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlphaFilteredYoFrameQuaternionTest +{ + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testInitialValue() + { + MutableDouble alpha = new MutableDouble(); + AlphaFilteredYoFrameQuaternion q = createAlphaFilteredYoFrameQuaternion(() -> alpha.doubleValue()); + + // set measurement randomly + Random random = new Random(12351235L); + Quaternion qMeasured = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qMeasured); + + // call update once + q.update(); + Quaternion qFiltered = new Quaternion(q); + + // verify measurement equals filtered + EuclidCoreTestTools.assertEquals(qMeasured, qFiltered, 1e-12); + } + + @Test + public void testAlpha1() + { + MutableDouble alpha = new MutableDouble(); + AlphaFilteredYoFrameQuaternion q = createAlphaFilteredYoFrameQuaternion(() -> alpha.doubleValue()); + alpha.setValue(1.0); + + Random random = new Random(73464L); + + // update once + Quaternion qInitial = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qInitial); + q.update(); + + // update 100 more times + int nUpdates = 100; + doRandomUpdates(q, random, nUpdates); + + Quaternion qFiltered = new Quaternion(q); + + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(qInitial, qFiltered, 1e-12); + } + + @Test + public void testAlpha0() + { + MutableDouble alpha = new MutableDouble(); + AlphaFilteredYoFrameQuaternion q = createAlphaFilteredYoFrameQuaternion(() -> alpha.doubleValue()); + alpha.setValue(0.0); + + Random random = new Random(12525123L); + + // update 100 times + int nUpdates = 100; + doRandomUpdates(q, random, nUpdates); + + // update one more time + Quaternion qFinal = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qFinal); + q.update(); + + Quaternion qFiltered = new Quaternion(q); + + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(qFinal, qFiltered, 1e-12); + } + + @Test + public void testStepChange() + { + MutableDouble alpha = new MutableDouble(); + AlphaFilteredYoFrameQuaternion q = createAlphaFilteredYoFrameQuaternion(() -> alpha.doubleValue()); + alpha.setValue(0.5); + + Random random = new Random(12525123L); + + // update once + Quaternion qInitial = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qInitial); + q.update(); + + // update a whole bunch of times using the same quaternion + Quaternion qFinal = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qFinal); + + double angleDifference = getAngleDifference(qInitial, qFinal); + double epsilon = 1e-3; + + int nUpdates = 100; + Quaternion qFiltered = new Quaternion(); + for (int i = 0; i < nUpdates; i++) + { + q.update(); + qFiltered.set(q); + double newAngleDifference = getAngleDifference(qFiltered, qFinal); + // System.out.println(i + ": " + newAngleDifference); + boolean sameQuaternion = newAngleDifference == 0.0; + assertTrue(sameQuaternion || newAngleDifference < (1.0 + epsilon) * alpha.doubleValue() * angleDifference); + angleDifference = newAngleDifference; + } + } + + private void doRandomUpdates(AlphaFilteredYoFrameQuaternion q, Random random, int nUpdates) + { + for (int i = 0; i < nUpdates; i++) + { + // set measurement randomly and updated filtered version + Quaternion qMeasured = EuclidCoreRandomTools.nextQuaternion(random); + q.getUnfilteredQuaternion().set(qMeasured); + q.update(); + } + } + + private AlphaFilteredYoFrameQuaternion createAlphaFilteredYoFrameQuaternion(DoubleProvider alpha) + { + YoRegistry registry = new YoRegistry("test"); + ReferenceFrame referenceFrame = ReferenceFrame.getWorldFrame(); + YoFrameQuaternion unfilteredQuaternion = new YoFrameQuaternion("qMeasured", referenceFrame, registry); + AlphaFilteredYoFrameQuaternion q = new AlphaFilteredYoFrameQuaternion("qFiltered", "", unfilteredQuaternion, alpha, registry); + return q; + } + + private static double getAngleDifference(Quaternion q1, Quaternion q2) + { + Quaternion qDifference = new Quaternion(q1); + qDifference.multiplyConjugateOther(q2); + AxisAngle axisAngle = new AxisAngle(); + axisAngle.set(qDifference); + return axisAngle.getAngle(); + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2DTest.java new file mode 100644 index 00000000..38aefa84 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector2DTest.java @@ -0,0 +1,54 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple2D.Vector2D; +import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class AlphaFilteredYoFrameVector2DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + AlphaFilteredYoFrameVector2D filteredVector = new AlphaFilteredYoFrameVector2D("tested", "", registry, alpha, ReferenceFrame.getWorldFrame()); + AlphaFilteredYoVariable xFiltered = new AlphaFilteredYoVariable("xRef", registry, alpha); + AlphaFilteredYoVariable yFiltered = new AlphaFilteredYoVariable("yRef", registry, alpha); + + Vector2D unfilteredVector = new Vector2D(); + + for (int j = 0; j < 10; j++) + { + unfilteredVector.add(EuclidCoreRandomTools.nextVector2D(random, 0.0, 0.5)); + + filteredVector.update(unfilteredVector); + xFiltered.update(unfilteredVector.getX()); + yFiltered.update(unfilteredVector.getY()); + + EuclidCoreTestTools.assertEquals(new Vector2D(xFiltered.getValue(), yFiltered.getValue()), filteredVector, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3DTest.java new file mode 100644 index 00000000..3d4a6c3f --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/AlphaFilteredYoFrameVector3DTest.java @@ -0,0 +1,57 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.yoVariables.filters.AlphaFilteredYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class AlphaFilteredYoFrameVector3DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + AlphaFilteredYoFrameVector3D filteredVector = new AlphaFilteredYoFrameVector3D("tested", "", registry, alpha, ReferenceFrame.getWorldFrame()); + AlphaFilteredYoVariable xFiltered = new AlphaFilteredYoVariable("xRef", registry, alpha); + AlphaFilteredYoVariable yFiltered = new AlphaFilteredYoVariable("yRef", registry, alpha); + AlphaFilteredYoVariable zFiltered = new AlphaFilteredYoVariable("zRef", registry, alpha); + + Vector3D unfilteredVector = new Vector3D(); + + for (int j = 0; j < 10; j++) + { + unfilteredVector.add(EuclidCoreRandomTools.nextPoint3D(random, 0.0, 0.5)); + + filteredVector.update(unfilteredVector); + xFiltered.update(unfilteredVector.getX()); + yFiltered.update(unfilteredVector.getY()); + zFiltered.update(unfilteredVector.getZ()); + + EuclidCoreTestTools.assertEquals(new Point3D(xFiltered.getValue(), yFiltered.getValue(), zFiltered.getValue()), filteredVector, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/EuclidCoreTestMissingTools.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/EuclidCoreTestMissingTools.java new file mode 100644 index 00000000..6b8ff355 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/EuclidCoreTestMissingTools.java @@ -0,0 +1,34 @@ +package us.ihmc.yoVariables.euclid.filters; + +import us.ihmc.euclid.tools.EuclidCoreIOTools; +import us.ihmc.euclid.transform.RigidBodyTransform; + +public class EuclidCoreTestMissingTools +{ + public static String toStringFullPrecision(RigidBodyTransform rigidBodyTransform) + { + return EuclidCoreIOTools.getRigidBodyTransformString(EuclidCoreIOTools.getStringFormat(18, 18), rigidBodyTransform); + } + + public static RigidBodyTransform newRigidBodyTransformFromString(String rigidBodyTransformAsString) + { + String[] lines = rigidBodyTransformAsString.split("\\R"); // Split the input by newlines [3] + String[] tokens; + double[] doubles = new double[lines.length * 4]; // Initialize an array to store the doubles + int index = 0; + + for (String line : lines) + { + line = line.replace("|", "").trim(); // Remove the '|' character and trim whitespace + tokens = line.split("\\s+"); // Split the line by whitespace [2] + + for (String token : tokens) + { + doubles[index++] = Double.parseDouble(token); // Convert the token to a double and store it in the array [1] + } + } + + RigidBodyTransform rigidBodyTransform = new RigidBodyTransform(); + return new RigidBodyTransform(doubles); + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector2DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector2DTest.java new file mode 100644 index 00000000..058a6e13 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector2DTest.java @@ -0,0 +1,55 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple2D.Point2D; +import us.ihmc.yoVariables.filters.FilteredFiniteDifferenceYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class FilteredVelocityYoFrameVector2DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double dt = EuclidCoreRandomTools.nextDouble(random, 1.0e-7, 1.0e-1); + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + FilteredFiniteDifferenceYoFrameVector2D filteredPoint = new FilteredFiniteDifferenceYoFrameVector2D("tested", "", () -> alpha, dt, registry, ReferenceFrame.getWorldFrame()); + FilteredFiniteDifferenceYoVariable xFiltered = new FilteredFiniteDifferenceYoVariable("xRef", "", alpha, dt, registry); + FilteredFiniteDifferenceYoVariable yFiltered = new FilteredFiniteDifferenceYoVariable("yRef", "", alpha, dt, registry); + + Point2D unfilteredPoint = new Point2D(); + + for (int j = 0; j < 10; j++) + { + unfilteredPoint.scaleAdd(dt, EuclidCoreRandomTools.nextPoint2D(random), unfilteredPoint); + + filteredPoint.update(unfilteredPoint); + xFiltered.update(unfilteredPoint.getX()); + yFiltered.update(unfilteredPoint.getY()); + + EuclidCoreTestTools.assertEquals(new Point2D(xFiltered.getValue(), yFiltered.getValue()), filteredPoint, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector3DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector3DTest.java new file mode 100644 index 00000000..993193d2 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/FilteredVelocityYoFrameVector3DTest.java @@ -0,0 +1,57 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.yoVariables.filters.FilteredFiniteDifferenceYoVariable; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class FilteredVelocityYoFrameVector3DTest +{ + private static final double EPSILON = 1.0e-15; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConsistencyWithAlphaFilteredYoVariable() + { + Random random = new Random(3453456); + + for (int i = 0; i < 100; i++) + { + double dt = EuclidCoreRandomTools.nextDouble(random, 1.0e-7, 1.0e-1); + double alpha = random.nextDouble(); + YoRegistry registry = new YoRegistry("blop"); + + FilteredFiniteDifferenceYoFrameVector3D filteredPoint = new FilteredFiniteDifferenceYoFrameVector3D("tested", "", () -> alpha, dt, registry, ReferenceFrame.getWorldFrame()); + FilteredFiniteDifferenceYoVariable xFiltered = new FilteredFiniteDifferenceYoVariable("xRef", "", alpha, dt, registry); + FilteredFiniteDifferenceYoVariable yFiltered = new FilteredFiniteDifferenceYoVariable("yRef", "", alpha, dt, registry); + FilteredFiniteDifferenceYoVariable zFiltered = new FilteredFiniteDifferenceYoVariable("zRef", "", alpha, dt, registry); + + Point3D unfilteredPoint = new Point3D(); + + for (int j = 0; j < 10; j++) + { + unfilteredPoint.scaleAdd(dt, EuclidCoreRandomTools.nextPoint3D(random), unfilteredPoint); + + filteredPoint.update(unfilteredPoint); + xFiltered.update(unfilteredPoint.getX()); + yFiltered.update(unfilteredPoint.getY()); + zFiltered.update(unfilteredPoint.getZ()); + + EuclidCoreTestTools.assertEquals(new Point3D(xFiltered.getValue(), yFiltered.getValue(), zFiltered.getValue()), filteredPoint, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientationTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientationTest.java new file mode 100644 index 00000000..b2a83fdd --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameOrientationTest.java @@ -0,0 +1,81 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.apache.commons.lang3.mutable.MutableDouble; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.AngleTools; +import us.ihmc.commons.RandomNumbers; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; + +import static org.junit.jupiter.api.Assertions.*; + +public class RateLimitedYoFrameOrientationTest +{ + private static final double EPSILON = 2.0e-11; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConvergenceWithConstantInput() throws Exception + { + Random random = new Random(46363); + double dt = 0.004; + MutableDouble maxRate = new MutableDouble(); + YoRegistry registry = new YoRegistry("dummy"); + RateLimitedYoFrameOrientation rateLimitedOrientation = new RateLimitedYoFrameOrientation("blop", "", registry, + (DoubleProvider) () -> maxRate.doubleValue(), dt, + ReferenceFrame.getWorldFrame()); + rateLimitedOrientation.update(new Quaternion()); + + FiniteDifferenceAngularVelocityYoFrameVector3D angularVelocity = new FiniteDifferenceAngularVelocityYoFrameVector3D("rate", ReferenceFrame.getWorldFrame(), + dt, registry); + + for (int i = 0; i < 1000; i++) + { + maxRate.setValue(RandomNumbers.nextDouble(random, 0.001, 10.0)); + Quaternion goalQuaternion = EuclidCoreRandomTools.nextQuaternion(random); + + double distanceToGoal = Math.abs(AngleTools.trimAngleMinusPiToPi(new Quaternion(rateLimitedOrientation).distance(goalQuaternion))); + if (distanceToGoal / dt < maxRate.doubleValue()) + { // Should converge in one step + rateLimitedOrientation.update(goalQuaternion); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(goalQuaternion, new Quaternion(rateLimitedOrientation), EPSILON); + } + else + { + double timeToConverge = distanceToGoal / maxRate.doubleValue(); + int numberOfIterations = (int) (timeToConverge / dt); + double previousDistance = distanceToGoal; + angularVelocity.update(rateLimitedOrientation); + + for (int j = 0; j < numberOfIterations; j++) + { + rateLimitedOrientation.update(goalQuaternion); + double distance = Math.abs(AngleTools.trimAngleMinusPiToPi(new Quaternion(rateLimitedOrientation).distance(goalQuaternion))); + assertTrue(distance < previousDistance); + angularVelocity.update(rateLimitedOrientation); + double rate = angularVelocity.norm(); + assertEquals(rate, maxRate.doubleValue(), EPSILON, "difference: " + Math.abs(rate - maxRate.doubleValue())); + previousDistance = distance; + assertFalse(new Quaternion(rateLimitedOrientation).geometricallyEquals(goalQuaternion, EPSILON)); + } + + rateLimitedOrientation.update(goalQuaternion); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(goalQuaternion, new Quaternion(rateLimitedOrientation), EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternionTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternionTest.java new file mode 100644 index 00000000..71fd7cce --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/RateLimitedYoFrameQuaternionTest.java @@ -0,0 +1,79 @@ +package us.ihmc.yoVariables.euclid.filters; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Random; + +import org.apache.commons.lang3.mutable.MutableDouble; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.AngleTools; +import us.ihmc.commons.RandomNumbers; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.yoVariables.providers.DoubleProvider; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class RateLimitedYoFrameQuaternionTest +{ + private static final double EPSILON = 1.0e-12; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testConvergenceWithConstantInput() + { + Random random = new Random(46363); + double dt = 0.004; + MutableDouble maxRate = new MutableDouble(); + YoRegistry registry = new YoRegistry("dummy"); + RateLimitedYoFrameQuaternion rateLimitedQuaternion = new RateLimitedYoFrameQuaternion("blop", "", registry, (DoubleProvider) () -> maxRate.doubleValue(), + dt, ReferenceFrame.getWorldFrame()); + rateLimitedQuaternion.update(new Quaternion()); + + FiniteDifferenceAngularVelocityYoFrameVector3D angularVelocity = new FiniteDifferenceAngularVelocityYoFrameVector3D("rate", rateLimitedQuaternion, dt, registry); + + for (int i = 0; i < 1000; i++) + { + maxRate.setValue(RandomNumbers.nextDouble(random, 0.001, 10.0)); + Quaternion goalQuaternion = EuclidCoreRandomTools.nextQuaternion(random); + + double distanceToGoal = Math.abs(AngleTools.trimAngleMinusPiToPi(rateLimitedQuaternion.distance(goalQuaternion))); + if (distanceToGoal / dt < maxRate.doubleValue()) + { // Should converge in one step + rateLimitedQuaternion.update(goalQuaternion); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(goalQuaternion, rateLimitedQuaternion, EPSILON); + } + else + { + double timeToConverge = distanceToGoal / maxRate.doubleValue(); + int numberOfIterations = (int) (timeToConverge / dt); + double previousDistance = distanceToGoal; + angularVelocity.update(); + + for (int j = 0; j < numberOfIterations; j++) + { + rateLimitedQuaternion.update(goalQuaternion); + double distance = Math.abs(AngleTools.trimAngleMinusPiToPi(rateLimitedQuaternion.distance(goalQuaternion))); + assertTrue(distance < previousDistance); + angularVelocity.update(); + double rate = angularVelocity.norm(); + assertEquals(rate, maxRate.doubleValue(), EPSILON, "difference: " + Math.abs(rate - maxRate.doubleValue())); + previousDistance = distance; + assertFalse(rateLimitedQuaternion.geometricallyEquals(goalQuaternion, EPSILON)); + } + + rateLimitedQuaternion.update(goalQuaternion); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(goalQuaternion, rateLimitedQuaternion, EPSILON); + } + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3DTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3DTest.java new file mode 100644 index 00000000..cad057e3 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/RunningAverageYoFrameVector3DTest.java @@ -0,0 +1,44 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.apache.commons.math3.stat.descriptive.moment.Mean; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.ReferenceFrame; + +import static org.junit.jupiter.api.Assertions.*; + +public class RunningAverageYoFrameVector3DTest +{ + @Test + public void testAgainstApacheMean() + { + Random random = new Random(1); + RunningAverageYoFrameVector3D yoAverage = new RunningAverageYoFrameVector3D("", ReferenceFrame.getWorldFrame(), null); + Mean meanX = new Mean(); + Mean meanY = new Mean(); + Mean meanZ = new Mean(); + double nextX = -10.0; + double nextY = -10.0; + double nextZ = -10.0; + + for (int i = 0; i < 1000; i++) + { + nextX += random.nextDouble(); + nextY += random.nextDouble(); + nextZ += random.nextDouble(); + yoAverage.update(nextX, nextY, nextZ); + meanX.increment(nextX); + meanY.increment(nextY); + meanZ.increment(nextZ); + + assertEquals(meanX.getResult(), yoAverage.getX()); + assertEquals(meanY.getResult(), yoAverage.getY()); + assertEquals(meanZ.getResult(), yoAverage.getZ()); + assertEquals(meanX.getN(), yoAverage.getSampleSize()); + assertEquals(meanY.getN(), yoAverage.getSampleSize()); + assertEquals(meanZ.getN(), yoAverage.getSampleSize()); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/euclid/filters/YoMatrixTest.java b/src/test/java/us/ihmc/yoVariables/euclid/filters/YoMatrixTest.java new file mode 100644 index 00000000..9847d094 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/euclid/filters/YoMatrixTest.java @@ -0,0 +1,114 @@ +package us.ihmc.yoVariables.euclid.filters; + +import java.util.Random; + +import org.ejml.EjmlUnitTests; +import org.ejml.data.DMatrixRMaj; +import org.ejml.dense.row.RandomMatrices_DDRM; +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.filters.YoMatrix; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class YoMatrixTest +{ + + + @Test + public void testYoMatrixSetTooBig() + { + int maxNumberOfRows = 4; + int maxNumberOfColumns = 8; + String name = "testMatrix"; + YoRegistry registry = new YoRegistry("testRegistry"); + YoMatrix yoMatrix = new YoMatrix(name, maxNumberOfRows, maxNumberOfColumns, registry); + + DMatrixRMaj tooBigMatrix = new DMatrixRMaj(maxNumberOfRows + 1, maxNumberOfColumns); + + try + { + yoMatrix.set(tooBigMatrix); + fail("Too Big"); + } + catch (RuntimeException e) + { + } + + tooBigMatrix = new DMatrixRMaj(maxNumberOfRows, maxNumberOfColumns + 1); + + try + { + yoMatrix.set(tooBigMatrix); + fail("Too Big"); + } + catch (RuntimeException e) + { + } + + // Test a 0 X Big Matrix + DMatrixRMaj okMatrix = new DMatrixRMaj(0, maxNumberOfColumns + 10); + yoMatrix.set(okMatrix); + assertMatrixYoVariablesAreNaN(name, maxNumberOfRows, maxNumberOfColumns, registry); + + DMatrixRMaj checkMatrix = new DMatrixRMaj(1, 1); + yoMatrix.getAndReshape(checkMatrix); + + assertEquals(0, checkMatrix.getNumRows()); + assertEquals(maxNumberOfColumns + 10, checkMatrix.getNumCols()); + + // Test a Big X 0 Matrix + + okMatrix = new DMatrixRMaj(maxNumberOfRows + 10, 0); + yoMatrix.set(okMatrix); + assertMatrixYoVariablesAreNaN(name, maxNumberOfRows, maxNumberOfColumns, registry); + + checkMatrix = new DMatrixRMaj(1, 1); + yoMatrix.getAndReshape(checkMatrix); + + assertEquals(maxNumberOfRows + 10, checkMatrix.getNumRows()); + assertEquals(0, checkMatrix.getNumCols()); + + } + + + private void checkMatrixYoVariablesEqualsCheckMatrixAndOutsideValuesAreNaN(String name, int maxNumberOfRows, int maxNumberOfColumns, DMatrixRMaj checkMatrix, YoRegistry registry) + { + int smallerRows = checkMatrix.getNumRows(); + int smallerColumns = checkMatrix.getNumCols(); + + // Make sure the values are correct, including values outside the range should be NaN: + for (int row = 0; row < maxNumberOfRows; row++) + { + for (int column = 0; column < maxNumberOfColumns; column++) + { + YoDouble variable = (YoDouble) registry.findVariable(YoMatrix.getFieldName(name, row, column)); + + if ((row < smallerRows) && (column < smallerColumns)) + { + assertEquals(checkMatrix.get(row, column), variable.getDoubleValue(), 1e-10); + } + else + { + assertTrue(Double.isNaN(variable.getDoubleValue()), "Values outside aren't NaN, instead are " + variable.getDoubleValue()); + } + + } + } + } + + private void assertMatrixYoVariablesAreNaN(String name, int maxNumberOfRows, int maxNumberOfColumns, YoRegistry registry) + { + for (int row = 0; row < maxNumberOfRows; row++) + { + for (int column = 0; column < maxNumberOfColumns; column++) + { + YoDouble variable = (YoDouble) registry.findVariable(YoMatrix.getFieldName(name, row, column)); + assertTrue(Double.isNaN(variable.getDoubleValue())); + } + } + } + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariableTest.java new file mode 100644 index 00000000..01462b6d --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/AlphaBetaFilteredYoVariableTest.java @@ -0,0 +1,70 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlphaBetaFilteredYoVariableTest +{ + private static final double DT = 0.1; + + @Test + public void testAlphaBetaFilteredVelocityAndPositionEstimatesWithNoVelocity() + { + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + YoDouble xMeasuredVariable = new YoDouble("xMeasuredVariable", registry); + + double alpha = 0.2; + double beta = 0.35; + + AlphaBetaFilteredYoVariable abFilteredYoVariable = new AlphaBetaFilteredYoVariable("abFilteredYoVariable", registry, alpha, beta, positionVariable, + xMeasuredVariable, DT); + + abFilteredYoVariable.set(0); + positionVariable.set(0); + xMeasuredVariable.set(42); + + for (int i = 0; i < 10000; i++) + { + abFilteredYoVariable.update(); + } + + // Converges on 0 since position doesn't change (no dx/dt) + assertEquals(0, abFilteredYoVariable.getDoubleValue(), 1e-7); + + } + + @Test + public void testAlphaBetaFilteredVelocityAndPositionEstimatesWithConstantVelocity() + { + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + YoDouble xMeasuredVariable = new YoDouble("xMeasuredVariable", registry); + + double alpha = 0.2; + double beta = 0.35; + + AlphaBetaFilteredYoVariable abFilteredYoVariable = new AlphaBetaFilteredYoVariable("abFilteredYoVariable", registry, alpha, beta, positionVariable, + xMeasuredVariable, DT); + + for (int i = 0; i < 10000; i++) + { + abFilteredYoVariable.set(0); + positionVariable.set(0); + xMeasuredVariable.set(42); + + for (int j = 0; j < 10000; j++) + { + xMeasuredVariable.set(xMeasuredVariable.getDoubleValue() + 10); // Velocity = 100 distances per time + abFilteredYoVariable.update(); + } + + assertEquals(100, abFilteredYoVariable.getDoubleValue(), 1e-7); + } + } + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariableTest.java new file mode 100644 index 00000000..5ae32fb7 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredWrappingYoVariableTest.java @@ -0,0 +1,218 @@ +package us.ihmc.yoVariables.filters; + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.RandomNumbers; +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlphaFilteredWrappingYoVariableTest +{ + private static final boolean DEBUG = false; + private final Random random = new Random(); + + @Test + public void testInputModulo() + { + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble alpha = new YoDouble("alpha", registry); + alpha.set(0.0); //sets the alpha to 0.0 so that the correction is instantaneous to check the result which is in fact the input with modulo + + YoDouble positionVariable = new YoDouble("positionVariable", registry); + AlphaFilteredWrappingYoVariable alphaFilteredWrappingYoVariable = new AlphaFilteredWrappingYoVariable("alphaFilteredWrappingYoVariable", "", registry, positionVariable, alpha, -2.0, 8.0); + + //test at the boundaries + positionVariable.set(8.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), -2.0, 1e-10)); + + positionVariable.set(-2.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), -2.0, 1e-10)); + + //test when the input is over the upperLimit + positionVariable.set(33.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), 3.0, 1e-10)); + + positionVariable.set(38.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), -2.0, 1e-10)); + + positionVariable.set(42.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), 2.0, 1e-10)); + + //test when the input is under the lowerLimit + positionVariable.set(-22.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), -2.0, 1e-10)); + + positionVariable.set(-23.5); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), 6.5, 1e-10)); + + positionVariable.set(-42.0); + alphaFilteredWrappingYoVariable.update(); + assertTrue(MathTools.epsilonEquals(alphaFilteredWrappingYoVariable.getDoubleValue(), -2.0, 1e-10)); + } + + + @Test + public void testNoisyFixedPosition() + { + // Use a reasonably large alpha for a reasonably large amount of noise + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble alpha = new YoDouble("alpha", registry); + alpha.set(0.8); + + YoDouble positionVariable = new YoDouble("positionVariable", registry); + AlphaFilteredWrappingYoVariable alphaFilteredWrappingYoVariable = new AlphaFilteredWrappingYoVariable("alphaFilteredWrappingYoVariable", "", registry, positionVariable, alpha, 0.0, 20.0); + + double pseudoNoise = 0; + + positionVariable.set(10.0); + for (int i = 0; i < 10000; i++) + { + // Oscillate the position about some uniformly distributed fixed point slightly larger than 10 + if (i % 2 == 0) + { + pseudoNoise = random.nextDouble(); + } + positionVariable.add(Math.pow(-1, i) * pseudoNoise); + alphaFilteredWrappingYoVariable.update(); + } + + assertEquals(10.0, alphaFilteredWrappingYoVariable.getDoubleValue(), 1.0); + } + + @Test + public void testErrorAlwaysDecreases() + { + // Use a reasonably large alpha for a reasonably large amount of noise + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble alpha = new YoDouble("alpha", registry); + alpha.set(0.999999); + + YoDouble positionVariable = new YoDouble("positionVariable", registry); + double lowerLimit = RandomNumbers.nextDouble(random, -100.0, 100.0); + double upperLimit = RandomNumbers.nextDouble(random, -100.0, 100.0); + if(upperLimit < lowerLimit) + { + double temp = lowerLimit; + lowerLimit = upperLimit; + upperLimit = temp; + } + + AlphaFilteredWrappingYoVariable alphaFilteredWrappingYoVariable = new AlphaFilteredWrappingYoVariable("alphaFilteredWrappingYoVariable", "", registry, positionVariable, alpha, lowerLimit, upperLimit); + positionVariable.set(RandomNumbers.nextDouble(random, lowerLimit, upperLimit)); + alphaFilteredWrappingYoVariable.update(); + + for(int iteration = 0; iteration < 10000; iteration++) + { + positionVariable.set(RandomNumbers.nextDouble(random, lowerLimit, upperLimit)); + double lastError = getErrorConsideringWrap(alphaFilteredWrappingYoVariable.getDoubleValue(), positionVariable.getDoubleValue(),lowerLimit, upperLimit); + for (int convergeAlphaCount = 0; convergeAlphaCount < 100; convergeAlphaCount++) + { + alphaFilteredWrappingYoVariable.update(); + double currentError = getErrorConsideringWrap(alphaFilteredWrappingYoVariable.getDoubleValue(), positionVariable.getDoubleValue(),lowerLimit, upperLimit); + assertTrue(Math.abs(currentError) < Math.abs(lastError)); + } + } + } + + public double getErrorConsideringWrap(double current, double target, double lowerLimit, double upperLimit) + { + double range = upperLimit - lowerLimit; + if(target > upperLimit) + { + target = (target - lowerLimit) % range + lowerLimit; + } + + if(target < lowerLimit) + { + double offset = (target - upperLimit) % range; + target = offset + upperLimit; + } + + double standardError = target - current; + double wrappingError = 0.0; + if(target > current) + { + wrappingError = lowerLimit - current + target - upperLimit; + } + else + { + wrappingError = upperLimit - current + target - lowerLimit; + } + + if(Math.abs(standardError) < Math.abs(wrappingError)) + { + return standardError; + } + return wrappingError; + } + + @Test + public void testWrappingError() + { + double e = getErrorConsideringWrap(0.2,0.8,0.0,1.0); + assertEquals(-0.4, e, 0.001); + + e = getErrorConsideringWrap(0.8,0.2,0.0,1.0); + assertEquals(0.4, e, 0.001); + + e = getErrorConsideringWrap(0.0,0.4,0,1); + assertEquals(0.4, e, 0.001); + + e = getErrorConsideringWrap(-0.2,0.4,-1.0,1.0); + assertEquals(0.6, e, 0.001); + + e = getErrorConsideringWrap(-1.0,1.0,-1.0,1.0); + assertEquals(0.0, e, 0.001); + + e = getErrorConsideringWrap(1.0,-1.0,-1.0,1.0); + assertEquals(0.0, e, 0.001); + + e = getErrorConsideringWrap(0.4, 1.6,-1.0,1.0); + assertEquals(-0.8, e, 0.001); + + e = getErrorConsideringWrap(-0.4, -1.6,-1.0,1.0); + assertEquals(0.8, e, 0.001); + + e = getErrorConsideringWrap(0.4, -1.6,-1.0,1.0); + assertEquals(0.0, e, 0.001); + + e = getErrorConsideringWrap(0.2, 0.2,-1.0,1.0); + assertEquals(0.0, e, 0.001); + + e = getErrorConsideringWrap(-3.2, -4.0,-5.0,-1.0); + assertEquals(-0.8, e, 0.001); + + e = getErrorConsideringWrap(0.0, 0.0, -1.0, 1.0); + + } + + @Test + public void testAlphaAndBreakFrequencyComputations() + { + double DT = 0.1; + double randomAlpha = random.nextDouble(); + double computedBreakFrequency = AlphaFilteredWrappingYoVariable.computeBreakFrequencyGivenAlpha(randomAlpha, DT); + double computedAlpha = AlphaFilteredWrappingYoVariable.computeAlphaGivenBreakFrequencyProperly(computedBreakFrequency, DT); + + assertEquals(randomAlpha, computedAlpha, 1e-7); + assertEquals(computedBreakFrequency, AlphaFilteredWrappingYoVariable.computeBreakFrequencyGivenAlpha(computedAlpha, DT), 1e-7); + + if(DEBUG) + { + System.out.println("Random Alpha: " + randomAlpha); + System.out.println("Computed Alpha: " + AlphaFilteredWrappingYoVariable.computeAlphaGivenBreakFrequencyProperly(computedBreakFrequency, DT)); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariableTest.java new file mode 100644 index 00000000..43839c72 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/AlphaFilteredYoVariableTest.java @@ -0,0 +1,64 @@ +package us.ihmc.yoVariables.filters; + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlphaFilteredYoVariableTest +{ + private final Random rng = new Random(); + + @Test + public void testNoisyFixedPosition() + { + // Use a reasonably large alpha for a reasonably large amount of noise + double alpha = 0.8; + + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + AlphaFilteredYoVariable alphaFilteredYoVariable = new AlphaFilteredYoVariable("alphaFilteredYoVariable", registry, alpha, positionVariable); + + double pseudoNoise = 0; + + positionVariable.set(10); + for (int i = 0; i < 10000; i++) + { + // Oscillate the position about some uniformly distributed fixed point slightly larger than 10 + if (i % 2 == 0) + { + pseudoNoise = rng.nextDouble(); + } + positionVariable.add(Math.pow(-1, i) * pseudoNoise); + alphaFilteredYoVariable.update(); + } + + assertEquals(10, alphaFilteredYoVariable.getDoubleValue(), 1); + } + + @Test + public void testAlphaAndBreakFrequencyComputations() + { + for (int i = 0; i < 1000; i++) + { + double dt = rng.nextDouble(); + + double expectedAlpha = rng.nextDouble(); + double breakFrequency = AlphaFilteredYoVariable.computeBreakFrequencyGivenAlpha(expectedAlpha, dt); + double actualAlpha = AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(breakFrequency, dt); + + assertEquals(expectedAlpha, actualAlpha, 1e-10); + + double maxFrequency = 0.5 * 0.5 / dt; + double expectedBreakFrequency = maxFrequency * rng.nextDouble(); + double alpha = AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(expectedBreakFrequency, dt); + double actualBreakFrequency = AlphaFilteredYoVariable.computeBreakFrequencyGivenAlpha(alpha, dt); + + assertEquals(expectedBreakFrequency, actualBreakFrequency, 1e-7); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariableTest.java new file mode 100644 index 00000000..83f543e5 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/BacklashCompensatingVelocityYoVariableTest.java @@ -0,0 +1,373 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.commons.RandomNumbers; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class BacklashCompensatingVelocityYoVariableTest +{ + private static final double EPSILON = 1e-8; + + @Test + public void testWithoutBacklashOrFiltering1() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + BacklashCompensatingVelocityYoVariable unprocessed = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, dt, slopTime, registry); + + double rawPosition = 0.0, rawPositionPrevValue = 0.0; + unprocessed.update(rawPosition); + + for (int i = 0; i < 1000; i++) + { + rawPosition = RandomNumbers.nextDouble(rand, -100.0, 100.0); + unprocessed.update(rawPosition); + + double rawVelocity = (rawPosition - rawPositionPrevValue) / dt; + + assertEquals(rawVelocity, unprocessed.getDoubleValue(), EPSILON); + + rawPositionPrevValue = rawPosition; + } + } + + @Test + public void testWithoutBacklashOrFiltering2() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + YoDouble rawPosition = new YoDouble("rawPosition", registry); + BacklashCompensatingVelocityYoVariable unprocessed = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, rawPosition, dt, slopTime, + registry); + + double rawPositionPrevValue = 0.0; + unprocessed.update(); + + for (int i = 0; i < 1000; i++) + { + rawPosition.set(RandomNumbers.nextDouble(rand, -100.0, 100.0)); + unprocessed.update(); + + double rawVelocity = (rawPosition.getDoubleValue() - rawPositionPrevValue) / dt; + + assertEquals(rawVelocity, unprocessed.getDoubleValue(), EPSILON); + + rawPositionPrevValue = rawPosition.getDoubleValue(); + } + } + + @Test + public void testWithoutBacklash1() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.1, 1.0)); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + YoDouble rawPosition = new YoDouble("rawPosition", registry); + FilteredFiniteDifferenceYoVariable filtVelocity = new FilteredFiniteDifferenceYoVariable("filtVelocity", "", alphaVariable, rawPosition, dt, registry); + BacklashCompensatingVelocityYoVariable filteredOnly = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, dt, slopTime, registry); + + filtVelocity.update(); + filteredOnly.update(rawPosition.getDoubleValue()); + + for (int i = 0; i < 1000; i++) + { + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.1, 1.0)); + rawPosition.set(RandomNumbers.nextDouble(rand, -100.0, 100.0)); + filtVelocity.update(); + filteredOnly.update(rawPosition.getDoubleValue()); + + assertEquals(filtVelocity.getDoubleValue(), filteredOnly.getDoubleValue(), EPSILON); + } + } + + @Test + public void testWithoutBacklash2() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.0, 1.0)); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + YoDouble rawPosition = new YoDouble("rawPosition", registry); + FilteredFiniteDifferenceYoVariable filtVelocity = new FilteredFiniteDifferenceYoVariable("filtVelocity", "", alphaVariable, rawPosition, dt, registry); + BacklashCompensatingVelocityYoVariable filteredOnly = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, rawPosition, dt, slopTime, + registry); + + filtVelocity.update(); + filteredOnly.update(); + + for (int i = 0; i < 1000; i++) + { + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.1, 1.0)); + rawPosition.set(RandomNumbers.nextDouble(rand, -100.0, 100.0)); + filtVelocity.update(); + filteredOnly.update(); + + assertEquals(filtVelocity.getDoubleValue(), filteredOnly.getDoubleValue(), EPSILON); + } + } + + @Test + public void testVelocityPositiveWithoutCrossingZero2() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.0, 1.0)); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + YoDouble rawPosition = new YoDouble("rawPosition", registry); + FilteredFiniteDifferenceYoVariable filtVelocity = new FilteredFiniteDifferenceYoVariable("filtVelocity", "", alphaVariable, rawPosition, dt, registry); + BacklashCompensatingVelocityYoVariable backlashAndFiltered = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, rawPosition, dt, slopTime, + registry); + + filtVelocity.update(); + backlashAndFiltered.update(); + + // In this test, the position is only increasing, so there should be no backlash filtering that gets applied. + + double currentTime = 0.0; + for (int i = 0; i < 10000; i++) + { + slopTime.set(RandomNumbers.nextDouble(rand, 0.0, 10.0)); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.1, 1.0)); + rawPosition.add(RandomNumbers.nextDouble(rand, 0.0, 101.0)); + filtVelocity.update(); + backlashAndFiltered.update(); + +// if (currentTime > 2.0 * slopTime.getDoubleValue()) + assertEquals(filtVelocity.getDoubleValue(), backlashAndFiltered.getDoubleValue(), EPSILON); + + currentTime += dt; + } + } + + @Test + public void testVelocityNegativeWithoutCrossingZero2() + { + Random rand = new Random(1798L); + + YoRegistry registry = new YoRegistry("blop"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.0, 1.0)); + double dt = RandomNumbers.nextDouble(rand, 1e-8, 1.0); + YoDouble slopTime = new YoDouble("slop", registry); + YoDouble rawPosition = new YoDouble("rawPosition", registry); + FilteredFiniteDifferenceYoVariable filtVelocity = new FilteredFiniteDifferenceYoVariable("filtVelocity", "", alphaVariable, rawPosition, dt, registry); + BacklashCompensatingVelocityYoVariable backlashAndFiltered = new BacklashCompensatingVelocityYoVariable("", "", alphaVariable, rawPosition, dt, slopTime, + registry); + + filtVelocity.update(); + backlashAndFiltered.update(); + + for (int i = 0; i < 1000; i++) + { + slopTime.set(RandomNumbers.nextDouble(rand, 0.0, 100.0)); + alphaVariable.set(RandomNumbers.nextDouble(rand, 0.0, 1.0)); + rawPosition.sub(RandomNumbers.nextDouble(rand, 0.0, 101.0)); + filtVelocity.update(); + backlashAndFiltered.update(); + + assertEquals(filtVelocity.getDoubleValue(), backlashAndFiltered.getDoubleValue(), EPSILON); + } + } + + + + @Test + public void testNoisySignalAndMakeSureVelocityHasSignalContent() + { + Random random = new Random(1798L); + + YoRegistry registry = new YoRegistry("Registry"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + YoDouble slopTime = new YoDouble("slopTime", registry); + YoDouble cleanPosition = new YoDouble("cleanPosition", registry); + YoDouble noisyPosition = new YoDouble("noisyPosition", registry); + YoDouble cleanVelocity = new YoDouble("cleanVelocity", registry); + + + YoDouble reconstructedPosition = new YoDouble("reconstructedPosition", registry); + YoDouble reconstructedPosition2 = new YoDouble("reconstructedPosition2", registry); + + YoDouble totalReconstructedPositionError2 = new YoDouble("totalReconstructedPositionError2", registry); + + YoDouble averageReconstructedPositionError2 = new YoDouble("averageReconstructedPositionError2", registry); + + double dt = 0.001; + double totalTime = 5.0; + + double amplitude = 2.0; + double frequency = 1.0; + double noiseAmplitude = 0.01; + + slopTime.set(0.1); + alphaVariable.set(0.95); + + BacklashCompensatingVelocityYoVariable revisedBacklashCompensatingVelocity = new BacklashCompensatingVelocityYoVariable("bl_qd_velocity2", "", alphaVariable, noisyPosition, dt, slopTime, registry); + + reconstructedPosition2.set(amplitude); + +// SimulationConstructionSet scs = new SimulationConstructionSet(new Robot("Test")); +// scs.addYoVariableRegistry(registry); +// scs.startOnAThread(); + + for (double time = 0.0; time < totalTime; time = time + dt) + { + cleanPosition.set(amplitude * Math.cos(2.0 * Math.PI * frequency * time)); + cleanVelocity.set(-2.0 * Math.PI * amplitude * frequency * Math.sin(2.0 * Math.PI * frequency * time)); + + noisyPosition.set(cleanPosition.getDoubleValue()); + noisyPosition.add(RandomNumbers.nextDouble(random, noiseAmplitude)); + + revisedBacklashCompensatingVelocity.update(); + + reconstructedPosition2.add(revisedBacklashCompensatingVelocity.getDoubleValue() * dt); + + double positionError2 = reconstructedPosition2.getDoubleValue() - cleanPosition.getDoubleValue(); + totalReconstructedPositionError2.add(Math.abs(positionError2) * dt); + +// scs.tickAndUpdate(); + } + + averageReconstructedPositionError2.set(totalReconstructedPositionError2.getDoubleValue() / totalTime); + + // The original one doesn't do very well with noisy signals because it thinks the noise is backlash. + assertTrue(averageReconstructedPositionError2.getDoubleValue() < 0.25); + } + + @Test + public void testSignalWithBacklash() + { + YoRegistry registry = new YoRegistry("Registry"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + YoDouble slopTime = new YoDouble("slopTime", registry); + + YoDouble cleanPosition = new YoDouble("cleanPosition", registry); + YoDouble backlashyPosition = new YoDouble("backlashyPosition", registry); + YoDouble cleanVelocity = new YoDouble("cleanVelocity", registry); + + + YoDouble reconstructedPosition2 = new YoDouble("reconstructedPosition2", registry); + + YoDouble totalReconstructedPositionError2 = new YoDouble("totalReconstructedPositionError2", registry); + + YoDouble averageReconstructedPositionError2 = new YoDouble("averageReconstructedPositionError2", registry); + + double dt = 0.001; + double totalTime = 5.0; + + double amplitude = 2.0; + double frequency = 1.0; + double backlashAmount = 0.1; + + slopTime.set(0.1); + alphaVariable.set(0.95); + + BacklashCompensatingVelocityYoVariable revisedBacklashCompensatingVelocity = new BacklashCompensatingVelocityYoVariable("bl_qd_velocity2", "", alphaVariable, backlashyPosition, dt, slopTime, registry); + + reconstructedPosition2.set(amplitude); + +// SimulationConstructionSet scs = new SimulationConstructionSet(new Robot("Test")); +// scs.addYoVariableRegistry(registry); +// scs.startOnAThread(); + + for (double time = 0.0; time < totalTime; time = time + dt) + { + cleanPosition.set(amplitude * Math.cos(2.0 * Math.PI * frequency * time)); + cleanVelocity.set(-2.0 * Math.PI * amplitude * frequency * Math.sin(2.0 * Math.PI * frequency * time)); + + backlashyPosition.set(cleanPosition.getDoubleValue()); + if(cleanVelocity.getDoubleValue() > 0.0) + { + backlashyPosition.add(backlashAmount); + } + + revisedBacklashCompensatingVelocity.update(); + + reconstructedPosition2.add(revisedBacklashCompensatingVelocity.getDoubleValue() * dt); + + double positionError2 = reconstructedPosition2.getDoubleValue() - cleanPosition.getDoubleValue(); + totalReconstructedPositionError2.add(Math.abs(positionError2) * dt); + +// scs.tickAndUpdate(); + } + + averageReconstructedPositionError2.set(totalReconstructedPositionError2.getDoubleValue() / totalTime); + + assertTrue(averageReconstructedPositionError2.getDoubleValue() < 0.25); + } + + + @Test + public void testRemoveSquareWaveBacklash() + { + YoRegistry registry = new YoRegistry("Registry"); + YoDouble alphaVariable = new YoDouble("alpha", registry); + YoDouble slopTime = new YoDouble("slopTime", registry); + + YoDouble backlashyPosition = new YoDouble("backlashyPosition", registry); + + double dt = 0.001; + double totalTime = 5.0; + + double frequency = 30.0; + double backlashAmount = 0.1; + + slopTime.set(0.1); + alphaVariable.set(0.95); + + BacklashCompensatingVelocityYoVariable revisedBacklashCompensatingVelocity = new BacklashCompensatingVelocityYoVariable("bl_qd_velocity2", "", alphaVariable, backlashyPosition, dt, slopTime, registry); + +// SimulationConstructionSet scs = new SimulationConstructionSet(new Robot("Test")); +// scs.addYoVariableRegistry(registry); +// scs.startOnAThread(); + + // initialize the system to make sure it's resting up against one of the heads of slop. Previously without this, it was a lucky test. + backlashyPosition.set(0.0); + revisedBacklashCompensatingVelocity.update(); + backlashyPosition.set(backlashAmount); + for (int i = 0; i < 2.0 * slopTime.getDoubleValue() / dt; i++) + revisedBacklashCompensatingVelocity.update(); + + + for (double time = 0.0; time < totalTime; time = time + dt) + { + backlashyPosition.set(Math.cos(2.0 * Math.PI * frequency * time)); + if (backlashyPosition.getDoubleValue() > 0.0) + { + backlashyPosition.set(backlashAmount); + } + else + { + backlashyPosition.set(-backlashAmount); + } + + revisedBacklashCompensatingVelocity.update(); + + assertEquals(0.0, revisedBacklashCompensatingVelocity.getDoubleValue(), 1e-3); + +// scs.tickAndUpdate(); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariableTest.java new file mode 100644 index 00000000..ad9eefa9 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/BacklashProcessingYoVariableTest.java @@ -0,0 +1,46 @@ +package us.ihmc.yoVariables.filters; + + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.RandomNumbers; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class BacklashProcessingYoVariableTest +{ + @Test + public void testAgainstRevisedBacklash() + { + YoRegistry registry = new YoRegistry("dummy"); + YoDouble slopTime = new YoDouble("slopTime", registry); + double dt = 0.002; + YoDouble alpha = new YoDouble("alpha", registry); + alpha.set(AlphaFilteredYoVariable.computeAlphaGivenBreakFrequencyProperly(16.0, dt)); + YoDouble positionVariable = new YoDouble("rawPosition", registry); + FilteredFiniteDifferenceYoVariable velocityVariable = new FilteredFiniteDifferenceYoVariable("fd", "", alpha, positionVariable, dt, registry); + + BacklashProcessingYoVariable blToTest = new BacklashProcessingYoVariable("blTest", "", velocityVariable, dt, slopTime, registry); + + BacklashCompensatingVelocityYoVariable blExpected = new BacklashCompensatingVelocityYoVariable("blExpected", "", alpha, positionVariable, dt, slopTime, registry); + + Random random = new Random(561651L); + + for (double t = 0.0; t < 100.0; t += dt) + { + positionVariable.set(2.0 * Math.sin(2.0 * Math.PI * 10.0) + RandomNumbers.nextDouble(random, 1.0) * Math.sin(2.0 * Math.PI * 30.0 + 2.0 / 3.0 * Math.PI)); + + velocityVariable.update(); + + blToTest.update(); + blExpected.update(); + + assertEquals(blToTest.getDoubleValue(), blExpected.getDoubleValue(), 1.0e-10); + } + } + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariableTest.java new file mode 100644 index 00000000..accd30ac --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/ButterworthFilteredYoVariableTest.java @@ -0,0 +1,305 @@ +package us.ihmc.yoVariables.filters; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.RandomNumbers; +import us.ihmc.yoVariables.filters.ButterworthFilteredYoVariable.ButterworthFilterType; +import us.ihmc.yoVariables.registry.YoRegistry; + +public class ButterworthFilteredYoVariableTest +{ + @Test + public void testAlphaCompute() + { + Random random = new Random(0734454); + double epsilon = 1.0e-12; + + for (int i = 0; i < 5000; i++) + { + double dt = RandomNumbers.nextDouble(random, 1.0e-4, 1.0e-2); + double samplingFrequency = 1.0 / dt; + + double breakFrequencyIn, alpha, breakFrequencyOut; + + breakFrequencyIn = 0.0; + alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(breakFrequencyIn, dt); + assertEquals(1.0, alpha, epsilon); + breakFrequencyOut = ButterworthFilteredYoVariable.computeBreakFrequencyGivenAlpha(alpha, dt); + assertEquals(breakFrequencyIn, breakFrequencyOut, epsilon); + + breakFrequencyIn = RandomNumbers.nextDouble(random, 0.0, 0.25 * samplingFrequency); + alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(breakFrequencyIn, dt); + breakFrequencyOut = ButterworthFilteredYoVariable.computeBreakFrequencyGivenAlpha(alpha, dt); + assertEquals(breakFrequencyIn, breakFrequencyOut, epsilon); + + breakFrequencyIn = RandomNumbers.nextDouble(random, 0.25 * samplingFrequency, 10.0 * samplingFrequency); + alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(breakFrequencyIn, dt); + assertEquals(0.0, alpha, epsilon); + breakFrequencyOut = ButterworthFilteredYoVariable.computeBreakFrequencyGivenAlpha(alpha, dt); + assertEquals(0.25 * samplingFrequency, breakFrequencyOut, epsilon); + } + } + + @Test + public void testBreakFrequencyLowPassFilter() + { + double dt = 0.001; + double desiredBreakFrequency = 4.0; + double alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(desiredBreakFrequency, dt); + ButterworthFilteredYoVariable butterworthFilteredYoVariable = new ButterworthFilteredYoVariable("test", null, alpha, ButterworthFilterType.LOW_PASS); + + double actualBreakFrequency = findBreakFrequency(butterworthFilteredYoVariable, dt, dt); + double percentError = Math.abs(actualBreakFrequency - desiredBreakFrequency) / desiredBreakFrequency; + // TODO After a bunch of reading, I'm pretty sure the break frequency is properly calculated. + // The algorithm for finding the break frequency seems to be doing the right thing. + // We get a percent error of 147% for a break frequency of 100Hz, so I'm missing something somewhere... + assertEquals(0.0, percentError, 0.05); + + // Random random = new Random(45353); + // int numberOfCycles = 20; + // + // for (int i = 0; i < 1000; i++) + // { + // dt = 0.001; + // double samplingFrequency = 1.0 / dt; + // desiredBreakFrequency = RandomNumbers.nextDouble(random, 0.0, 0.25 * samplingFrequency); + // alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(desiredBreakFrequency, dt); + // + // butterworthFilteredYoVariable = new ButterworthFilteredYoVariable("test", null, alpha, ButterworthFilterType.LOW_PASS); + // + // + // double testBreakFrequency = desiredBreakFrequency; + // int length = (int) Math.max(Math.ceil(numberOfCycles / (testBreakFrequency * dt)) + 1, 1000); + // + // TimedData[] inputCurve = generateInputCurve(length, testBreakFrequency, dt); + // TimedData[] outputCurve = getFilteredCurve(inputCurve, butterworthFilteredYoVariable); + // + // inputCurve = Arrays.copyOfRange(inputCurve, inputCurve.length / 2, inputCurve.length); + // outputCurve = Arrays.copyOfRange(outputCurve, outputCurve.length / 2, outputCurve.length); + // + // double actualAttenuation = getMagnitudeInDecibels(inputCurve, outputCurve); + // double expectedAttenuation = -Math.abs(computePredictedAttenuationInDecibels(desiredBreakFrequency, 1, testBreakFrequency)); + // + // assertEquals(expectedAttenuation, actualAttenuation, 10.0 * dt, "Iteration: " + i + ", desiredBreakFrequency: " + desiredBreakFrequency); + // } + } + + public static double computePredictedAttenuationInDecibels(double cutOffFrequency, int filterOrder, double queryFrequency) + { + return 10.0 * Math.log10(1.0 + Math.pow(queryFrequency / cutOffFrequency, 2.0 * filterOrder)); + } + + @Disabled // Old code + @Test + public void testButterWorth() + { + YoRegistry registry = new YoRegistry("Test"); + double dt = 0.001; + double desiredBreakFrequency = 1.0; + double alpha = ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(desiredBreakFrequency, dt); + double testBreakFrequency = desiredBreakFrequency; + System.out.println(ButterworthFilteredYoVariable.computeAlphaGivenBreakFrequency(testBreakFrequency, dt)); + + ButterworthFilteredYoVariable butterworthFilteredYoVariable = new ButterworthFilteredYoVariable("test", registry, alpha, ButterworthFilterType.LOW_PASS); + + int numberOfCycles = 6; + double endTime = (numberOfCycles) / testBreakFrequency; + + TimedData[] inputCurve = generateInputCurve(endTime, testBreakFrequency, dt); + TimedData[] outputCurve = getFilteredCurve(inputCurve, butterworthFilteredYoVariable); + + TimedData[] clippedInputCurve = Arrays.copyOfRange(inputCurve, inputCurve.length / 2, inputCurve.length); + TimedData[] clippedOutputCurve = Arrays.copyOfRange(outputCurve, outputCurve.length / 2, outputCurve.length); + + double dB = getMagnitudeInDecibels(clippedInputCurve, clippedOutputCurve); + System.out.println("dB= " + dB); + System.out.println(computePredictedAttenuationInDecibels(desiredBreakFrequency, 1, testBreakFrequency)); + } + + private static TimedData[] generateInputCurve(double endTime, double frequency, double dt) + { + return generateInputCurve((int) Math.ceil(endTime / dt) + 1, frequency, dt); + } + + private static TimedData[] generateInputCurve(int numberOfElements, double frequency, double dt) + { + TimedData[] inputCurve = new TimedData[numberOfElements]; + + double time = 0.0; + + for (int i = 0; i < numberOfElements; i++) + { + inputCurve[i] = new TimedData(time, Math.sin(2.0 * Math.PI * frequency * time)); + time += dt; + } + + return inputCurve; + } + + private static double findBreakFrequency(ButterworthFilteredYoVariable filter, double dt, double tolerance) + { + int numberOfCycles = 10; + + double samplingFrequency = 1.0 / dt; + double upperBreakFrequency = 0.5 * samplingFrequency; + double lowerBreakFrequency = 0.0; + double upper_dB = Double.NEGATIVE_INFINITY; + double lower_dB = 0.0; + + double cutOff_dB = -computePredictedAttenuationInDecibels(samplingFrequency, 1, samplingFrequency); + + while (Math.abs(upper_dB - lower_dB) > tolerance && Math.abs(upperBreakFrequency - lowerBreakFrequency) > 0.01 * dt) + { + double testBreakFrequency = 0.5 * (upperBreakFrequency + lowerBreakFrequency); + double endTime = (numberOfCycles) / testBreakFrequency; + TimedData[] inputCurve = generateInputCurve(endTime, testBreakFrequency, dt); + TimedData[] outputCurve = getFilteredCurve(inputCurve, filter); + + TimedData[] clippedInputCurve = Arrays.copyOfRange(inputCurve, inputCurve.length / 2, inputCurve.length); + TimedData[] clippedOutputCurve = Arrays.copyOfRange(outputCurve, outputCurve.length / 2, outputCurve.length); + + double dB = getMagnitudeInDecibels(clippedInputCurve, clippedOutputCurve); + System.out.println("Test breakFrequency: " + testBreakFrequency + ", dB: " + dB); + + if (dB < cutOff_dB) + { + upperBreakFrequency = testBreakFrequency; + upper_dB = dB; + } + else + { + lowerBreakFrequency = testBreakFrequency; + lower_dB = dB; + } + } + + return 0.5 * (upperBreakFrequency + lowerBreakFrequency); + } + + private static TimedData[] getFilteredCurve(TimedData[] input, ButterworthFilteredYoVariable butterworthFilteredYoVariable) + { + TimedData[] filteredCurve = new TimedData[input.length]; + + butterworthFilteredYoVariable.reset(); + + for (int i = 0; i < input.length; i++) + { + butterworthFilteredYoVariable.update(input[i].value); + + filteredCurve[i] = new TimedData(input[i].time, butterworthFilteredYoVariable.getDoubleValue()); + } + + return filteredCurve; + } + + private static double plotBodeForAlpha(double alpha, double dt) + { + YoRegistry registry = new YoRegistry("Test"); + + ButterworthFilteredYoVariable butterworthFilteredYoVariable = new ButterworthFilteredYoVariable("test", registry, alpha, ButterworthFilterType.LOW_PASS); + + double startFreq = 1e-2; + double endFreq = 0.25 * (1.0 / dt); + + int numberOfTestPoints = 100; + + double deltaFreq = (endFreq - startFreq) / (numberOfTestPoints - 1); + + List testFrequencies = new ArrayList<>(); + List attenuations = new ArrayList<>(); + + for (double freq = startFreq; freq <= endFreq; freq = freq + deltaFreq) + { + int numberOfCycles = 3; + double endTime = (numberOfCycles) / freq; + + TimedData[] inputCurve = generateInputCurve(endTime, freq, dt); + TimedData[] outputCurve = getFilteredCurve(inputCurve, butterworthFilteredYoVariable); + + double dB = getMagnitudeInDecibels(inputCurve, outputCurve); + + testFrequencies.add(freq); + attenuations.add(dB); + } + + @SuppressWarnings("unused") + List testFrequenciesLog = convertToLog(testFrequencies); + + double breakFreq = getBreakFreq(testFrequencies, attenuations); + + return breakFreq; + } + + private static double getBreakFreq(List freq, List attenutation_dB) + { + for (int i = 0; i < freq.size(); i++) + { + double attenuation = attenutation_dB.get(i); + + if (attenuation < -3.0) + return freq.get(i); + } + + return Double.POSITIVE_INFINITY; + } + + private static ArrayList convertToLog(List arrayListToConvert) + { + ArrayList ret = new ArrayList<>(); + + for (Double value : arrayListToConvert) + { + double logValue = Math.log10(value); + ret.add(logValue); + } + + return ret; + } + + private static double getMagnitudeInDecibels(TimedData[] input, TimedData[] output) + { + double inputAmp = getMaximumPeakToPeakAmplitude(input); + double outputAmp = getMaximumPeakToPeakAmplitude(output); + double attenuation = outputAmp / inputAmp; + return 10.0 * Math.log10(attenuation); + } + + private static double getMaximumPeakToPeakAmplitude(TimedData[] dataset) + { + double value = dataset[0].value; + double maximumValue = value; + double minimumValue = value; + + for (int i = 1; i < dataset.length; i++) + { + value = dataset[1].value; + + if (value > maximumValue) + maximumValue = value; + else if (value < minimumValue) + minimumValue = value; + } + + return maximumValue - minimumValue; + } + + private static class TimedData + { + private double time; + private double value; + + public TimedData(double time, double value) + { + this.time = time; + this.value = value; + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/DeadbandedYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/DeadbandedYoVariableTest.java new file mode 100644 index 00000000..9b2af143 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/DeadbandedYoVariableTest.java @@ -0,0 +1,59 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.MathTools; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class DeadbandedYoVariableTest +{ + + @Test + public void testDeadband() + { + YoRegistry registry = new YoRegistry("test"); + YoDouble deadzoneSize = new YoDouble("deadzoneSize", registry); + YoDouble input = new YoDouble("input", registry); + double deadzone = 2.0; + deadzoneSize.set(deadzone); + DeadbandedYoVariable testDeadzone = new DeadbandedYoVariable("testDeadZone", input , deadzoneSize, registry); + + double verySmallStep = 1e-4; + + input.set(deadzone - verySmallStep); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), 0.0, 1e-14)); + input.set(deadzone + verySmallStep); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), verySmallStep, 1e-14)); + + input.set(-deadzone + verySmallStep); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), 0.0, 1e-14)); + input.set(-deadzone - verySmallStep); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), -verySmallStep, 1e-14)); + + for (double valueToBeCorrected = -10.0; valueToBeCorrected < -deadzone; valueToBeCorrected += 0.01) + { + input.set(valueToBeCorrected); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), valueToBeCorrected + deadzone, 1e-14)); + } + for (double valueToBeCorrected = -deadzone; valueToBeCorrected < deadzone; valueToBeCorrected += 0.01) + { + input.set(valueToBeCorrected); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), 0.0, 1e-14)); + } + for (double valueToBeCorrected = deadzone; valueToBeCorrected < 10.0; valueToBeCorrected += 0.01) + { + input.set(valueToBeCorrected); + testDeadzone.update(); + assertTrue(MathTools.epsilonEquals(testDeadzone.getDoubleValue(), valueToBeCorrected - deadzone, 1e-14)); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/DelayedYoBooleanTest.java b/src/test/java/us/ihmc/yoVariables/filters/DelayedYoBooleanTest.java new file mode 100644 index 00000000..e99d33b6 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/DelayedYoBooleanTest.java @@ -0,0 +1,190 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +public class DelayedYoBooleanTest +{ + private static YoRegistry registry; + private static YoBoolean variableToDelay; + private static Boolean DEBUG = false; + private int ticksToDelay; + + @BeforeEach + public void setUp() + { + registry = new YoRegistry("registry"); + variableToDelay = new YoBoolean("variableToDelay", registry); + } + + @AfterEach + public void tearDown() + { + registry = null; + variableToDelay = null; + } + + @Test + public void testDelayedYoVariableMultipleTickDelays() + { + for (ticksToDelay = 0; ticksToDelay < 10; ticksToDelay++) + { + variableToDelay.set(true); + + DelayedYoBoolean delayedYoVariable = new DelayedYoBoolean("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, + registry); + + int ticksToTest = 100; + boolean[] valuesToSet = new boolean[ticksToTest]; + + for (int i = 0; i < valuesToSet.length; i++) + { + if (Math.random() < .5) + valuesToSet[i] = true; + else + valuesToSet[i] = false; + } + + assertEquals(delayedYoVariable.getBooleanValue(), true); + + for (int i = 0; i < ticksToTest; i++) + { + variableToDelay.set(valuesToSet[i]); + delayedYoVariable.update(); + + if (i < ticksToDelay) + { + assertEquals(delayedYoVariable.getBooleanValue(), true); + } + else + { + assertEquals(delayedYoVariable.getBooleanValue(), valuesToSet[i - ticksToDelay]); + } + } + } + } + + @Test + public void testDelayedYoVariableOneTickDelay() + { + ticksToDelay = 1; + + variableToDelay.set(false); + DelayedYoBoolean delayedYoVariable = new DelayedYoBoolean("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + assertEquals(delayedYoVariable.getBooleanValue(), false); + + variableToDelay.set(true); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), false); + + variableToDelay.set(false); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + variableToDelay.set(true); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), false); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + } + + @Test + public void testDelayedYoVariableZeroTickDelay() + { + ticksToDelay = 0; + + variableToDelay.set(false); + DelayedYoBoolean delayedYoVariable = new DelayedYoBoolean("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + assertEquals(delayedYoVariable.getBooleanValue(), false); + + variableToDelay.set(true); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + variableToDelay.set(false); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), false); + + variableToDelay.set(true); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + } + + @Test + public void testUpdateWithZero() + { + ticksToDelay = 0; + variableToDelay.set(false); + DelayedYoBoolean delayedYoVariable = new DelayedYoBoolean("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + delayedYoVariable.getInternalState("Should be all false", DEBUG); + + variableToDelay.set(true); + delayedYoVariable.update(); + delayedYoVariable.getInternalState("Should be all true", DEBUG); + + assertEquals(delayedYoVariable.getBooleanValue(), true); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getBooleanValue(), true); + } + + @Test + public void testReset() + { + ticksToDelay = 10; + variableToDelay.set(false); + DelayedYoBoolean delayedYoVariable = new DelayedYoBoolean("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + + for(int i = 0; i < ticksToDelay; i++) + { + assertEquals(delayedYoVariable.getBooleanValue(), false); + delayedYoVariable.update(); + } + + delayedYoVariable.getInternalState("Should be all false", DEBUG); + + variableToDelay.set(true); + delayedYoVariable.update(); + + for(int i = 0; i < ticksToDelay; i++) + { + assertEquals(delayedYoVariable.getBooleanValue(), false); + delayedYoVariable.update(); + } + assertEquals(delayedYoVariable.getBooleanValue(), true); + + variableToDelay.set(false); + delayedYoVariable.update(); + delayedYoVariable.getInternalState("Should be all true, except for the end", DEBUG); + + delayedYoVariable.reset(); + delayedYoVariable.update(); + + for(int i = 0; i < ticksToDelay; i++) + { + assertEquals(delayedYoVariable.getBooleanValue(), false); + delayedYoVariable.update(); + } + delayedYoVariable.getInternalState("Should be all false", DEBUG); + } +} \ No newline at end of file diff --git a/src/test/java/us/ihmc/yoVariables/filters/DelayedYoDoubleTest.java b/src/test/java/us/ihmc/yoVariables/filters/DelayedYoDoubleTest.java new file mode 100644 index 00000000..e2c46f34 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/DelayedYoDoubleTest.java @@ -0,0 +1,121 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class DelayedYoDoubleTest +{ + + @Test + public void testDelayedYoVariableMultipleTickDelays() + { + YoRegistry registry = new YoRegistry("registry"); + YoDouble variableToDelay = new YoDouble("variableToDelay", registry); + + for (int ticksToDelay = 0; ticksToDelay < 10; ticksToDelay++) + { + double firstValue = Math.random(); + variableToDelay.set(firstValue); + + DelayedYoDouble delayedYoVariable = new DelayedYoDouble("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + + int ticksToTest = 100; + double[] valuesToSet = new double[ticksToTest]; + + for (int i = 0; i < valuesToSet.length; i++) + { + valuesToSet[i] = Math.random(); + } + + assertEquals(delayedYoVariable.getDoubleValue(), firstValue, 1e-7); + + for (int i = 0; i < ticksToTest; i++) + { + variableToDelay.set(valuesToSet[i]); + delayedYoVariable.update(); + + if (i < ticksToDelay) + { + assertEquals(delayedYoVariable.getDoubleValue(), firstValue, 1e-7); + } + else + { + assertEquals(delayedYoVariable.getDoubleValue(), valuesToSet[i - ticksToDelay], 1e-7); + } + } + } + + } + + @Test + public void testDelayedYoVariableOneTickDelay() + { + YoRegistry registry = new YoRegistry("registry"); + YoDouble variableToDelay = new YoDouble("variableToDelay", registry); + + int ticksToDelay = 1; + + variableToDelay.set(0.0); + DelayedYoDouble delayedYoVariable = new DelayedYoDouble("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + assertEquals(delayedYoVariable.getDoubleValue(), 0.0, 1e-7); + + variableToDelay.set(1.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 0.0, 1e-7); + + variableToDelay.set(2.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 1.0, 1e-7); + + variableToDelay.set(3.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 2.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + } + + @Test + public void testDelayedYoVariableZeroTickDelay() + { + YoRegistry registry = new YoRegistry("registry"); + YoDouble variableToDelay = new YoDouble("variableToDelay", registry); + + int ticksToDelay = 0; + + variableToDelay.set(0.0); + DelayedYoDouble delayedYoVariable = new DelayedYoDouble("delayedVariable" + ticksToDelay, "", variableToDelay, ticksToDelay, registry); + assertEquals(delayedYoVariable.getDoubleValue(), 0.0, 1e-7); + + variableToDelay.set(1.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 1.0, 1e-7); + + variableToDelay.set(2.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 2.0, 1e-7); + + variableToDelay.set(3.0); + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + + delayedYoVariable.update(); + assertEquals(delayedYoVariable.getDoubleValue(), 3.0, 1e-7); + } + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariableTest.java new file mode 100644 index 00000000..31d7b044 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/DeltaLimitedYoVariableTest.java @@ -0,0 +1,400 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.commons.RandomNumbers; +import us.ihmc.yoVariables.registry.YoRegistry; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Doug Stephen (dstephen@ihmc.us) + */ +public class DeltaLimitedYoVariableTest +{ + private static final int RANDOM_LOWER_BOUND = 10; + private static final int RANDOM_UPPER_BOUND = 30000; + private YoRegistry registry; + private DeltaLimitedYoVariable variable; + + @Test + public void testReferenceAndInputBothNegativeNoOvershootInputGreaterThanReference() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference / 2.0; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input < 0.0); + assertTrue(reference < 0.0); + assertTrue(input > reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testReferenceAndInputBothNegativeNoOvershootReferenceGreaterThanInput() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference * 2; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input < 0.0); + assertTrue(reference < 0.0); + assertTrue(input < reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testReferenceAndInputBothPositiveNoOvershootReferenceGreaterThanInput() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference / 2.0; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input > 0.0); + assertTrue(reference > 0.0); + assertTrue(input < reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testReferenceAndInputBothPositiveNoOvershootInputGreaterThanReference() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference * 2; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input > 0.0); + assertTrue(reference > 0.0); + assertTrue(input > reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testPositiveReferenceNegativeInputNoOvershoot() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference * -0.5; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input < 0.0); + assertTrue(reference > 0.0); + assertTrue(input < reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testNegativeReferencePositiveInputNoOvershoot() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference * -0.5; + double delta = Math.abs(input - reference); + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + assertTrue(input > 0.0); + assertTrue(reference < 0.0); + assertTrue(input > reference); + assertFalse(variable.isLimitingActive()); + assertEquals(input, variable.getDoubleValue(), 1e-8); + } + } + + @Test + public void testReferenceAndInputBothNegativeWithOvershootInputGreaterThanReference() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference / 2.0; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input - overshoot; + + assertTrue(input < 0.0); + assertTrue(reference < 0.0); + assertTrue(input > reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testReferenceAndInputBothNegativeWithOvershootReferenceGreaterThanInput() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference * 2; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input + overshoot; + + assertTrue(input < 0.0); + assertTrue(reference < 0.0); + assertTrue(input < reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testReferenceAndInputBothPositiveWithOvershootInputGreaterThanReference() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference * 2; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input - overshoot; + + assertTrue(input > 0.0); + assertTrue(reference > 0.0); + assertTrue(input > reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testReferenceAndInputBothPositiveWithOvershootReferenceGreaterThanInput() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference / 2.0; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input + overshoot; + + assertTrue(input > 0.0); + assertTrue(reference > 0.0); + assertTrue(input < reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testPositiveReferenceNegativeInputWithOvershoot() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND); + double input = reference * -0.5; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input + overshoot; + + assertTrue(input < 0.0); + assertTrue(reference > 0.0); + assertTrue(input < reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testNegativeReferencePositiveInputWithOvershoot() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference * -0.5; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input - overshoot; + + assertTrue(input > 0.0); + assertTrue(reference < 0.0); + assertTrue(input > reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } + + @Test + public void testOvershootThenNoOvershoot() + { + Random random = new Random(1976L); + registry = new YoRegistry("registry"); + variable = new DeltaLimitedYoVariable("testVar", registry, 0.0); + + for (int i = 0; i < 60000; i++) + { + double reference = RandomNumbers.nextInt(random, RANDOM_LOWER_BOUND, RANDOM_UPPER_BOUND) * -1.0; + double input = reference * -0.5; + double delta = Math.abs(input - reference) / 2.0; + + variable.setMaxDelta(delta); + variable.updateOutput(reference, input); + + double overshoot = Math.abs(input - reference) - delta; + + double expectedClip = input - overshoot; + + assertTrue(input > 0.0); + assertTrue(reference < 0.0); + assertTrue(input > reference); + assertTrue(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + + input = variable.getDoubleValue(); + + variable.updateOutput(reference, input); + double newRequestedDelta = Math.abs(input - reference); + assertFalse(newRequestedDelta > delta); + assertFalse(variable.isLimitingActive()); + assertEquals(expectedClip, + variable.getDoubleValue(), + 1e-8, + "Variable not clipped correctly\nReference: " + reference + "\nInput: " + input + "\nMagnitude of requested delta: " + Math.abs( + input - reference) + "\nMax Allowed Delta: " + delta + "\nOvershoot: " + overshoot); + } + } +} \ No newline at end of file diff --git a/src/test/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariableTest.java new file mode 100644 index 00000000..0fa77c87 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/FilteredDiscreteVelocityYoVariableTest.java @@ -0,0 +1,70 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class FilteredDiscreteVelocityYoVariableTest +{ + + private static final double DT = 0.1; + + @Test + public void testFilteredDiscreteVelocityNoDirectionChange() + { + double alpha = 0.99; + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + YoDouble time = new YoDouble("time", registry); + FilteredDiscreteVelocityYoVariable filteredDiscreteVelocityYoVariable = new FilteredDiscreteVelocityYoVariable("filteredDiscreteVelocityYoVariable", "", + alpha, positionVariable, time, registry); + + positionVariable.set(10); + time.set(0); + + for (int i = 0; i < 1000 / DT; i++) + { + time.add(DT); + positionVariable.add(1); + filteredDiscreteVelocityYoVariable.update(); + } + + assertEquals(10, filteredDiscreteVelocityYoVariable.getDoubleValue(), 1e-7); + } + + @Test + public void testFilteredDiscreteVelocityWithDirectionChange() + { + double alpha = 0.99; + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + YoDouble time = new YoDouble("time", registry); + FilteredDiscreteVelocityYoVariable filteredDiscreteVelocityYoVariable = new FilteredDiscreteVelocityYoVariable("filteredDiscreteVelocityYoVariable", "", + alpha, positionVariable, time, registry); + + positionVariable.set(10); + time.set(0); + + for (int i = 0; i < 1000 / DT; i++) + { + time.add(DT); + positionVariable.add(1); + filteredDiscreteVelocityYoVariable.update(); + } + + assertEquals(10, filteredDiscreteVelocityYoVariable.getDoubleValue(), 1e-7); + + for (int i = 0; i < 1000 / DT; i++) + { + time.add(DT); + positionVariable.add(-1); + filteredDiscreteVelocityYoVariable.update(); + } + + assertEquals(-10, filteredDiscreteVelocityYoVariable.getDoubleValue(), 1e-7); + } + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/FilteredVelocityYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/FilteredVelocityYoVariableTest.java new file mode 100644 index 00000000..fe06ca87 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/FilteredVelocityYoVariableTest.java @@ -0,0 +1,67 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class FilteredVelocityYoVariableTest +{ + + private static final double DT = 0.1; + + @Test + public void testUpdateForTranslationalVelocity() + { + YoRegistry registry = new YoRegistry("testRegistry"); + double alpha = 0.3; + YoDouble positionVariable = new YoDouble("positionVariable", registry); + + FilteredFiniteDifferenceYoVariable filteredVelocityYoVariable = new FilteredFiniteDifferenceYoVariable("filteredVelocityYoVariable", "test description", alpha, + positionVariable, DT, registry); + + filteredVelocityYoVariable.set(0); + positionVariable.set(0); + + for (int i = 0; i < 10000; i++) + { + positionVariable.add(10); + filteredVelocityYoVariable.update(); + } + + assertEquals(100, filteredVelocityYoVariable.getDoubleValue(), 1e-7); + + } + + @Test + public void testUpdateForRotationalVelocity() + { + YoRegistry registry = new YoRegistry("testRegistry"); + double alpha = 0.005; + YoDouble positionVariable = new YoDouble("positionVariable", registry); + + FilteredFiniteDifferenceYoVariable filteredVelocityYoVariable = new FilteredFiniteDifferenceYoVariable("filteredVelocityYoVariable", "test description", alpha, + positionVariable, DT, registry); + + filteredVelocityYoVariable.set(0); + positionVariable.set(-Math.PI); + + for (int i = 0; i < 10000; i++) + { + if (positionVariable.getValueAsDouble() + 0.5 > Math.PI) + { + positionVariable.set(-Math.PI + (Math.PI - positionVariable.getValueAsDouble())); + } + else + { + positionVariable.add(.5); + } + + filteredVelocityYoVariable.updateForAngles(); + } + + assertEquals(5, filteredVelocityYoVariable.getDoubleValue(), 1e-5); + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDoubleTest.java b/src/test/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDoubleTest.java new file mode 100644 index 00000000..2f8444c0 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/FirstOrderFilteredYoDoubleTest.java @@ -0,0 +1,238 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.yoVariables.filters.FirstOrderFilteredYoDouble.FirstOrderFilterType; +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FirstOrderFilteredYoDoubleTest +{ + private final YoRegistry registry = new YoRegistry("testRegistry"); + private final YoDouble yoTime = new YoDouble("yoTime", registry); + + private final double DT = 0.001; + + @Test + public void testHighPassAttenuationForSinusoidalInput() + { + double inputFrequencyRadPerSec = 15.0; + double cutoffFrequencyRadPerSec = inputFrequencyRadPerSec / 5.0; + double filterAttenuation = 1.0; + double properHighPassAttenuation; + + FirstOrderFilteredYoDouble highPassFilteredYoVariable = new FirstOrderFilteredYoDouble("highPass", + "", + cutoffFrequencyRadPerSec / (2.0 * Math.PI), + yoTime, + FirstOrderFilterType.HIGH_PASS, + registry); + + while (filterAttenuation > 0.1 && cutoffFrequencyRadPerSec > 0.0) + { + highPassFilteredYoVariable.setCutoffFrequencyHz(cutoffFrequencyRadPerSec / (2.0 * Math.PI)); + highPassFilteredYoVariable.reset(); + filterAttenuation = computeSteadyStateFilteredOutputAmplitude(yoTime, DT, inputFrequencyRadPerSec, highPassFilteredYoVariable); + + properHighPassAttenuation = computeProperHighPassAttenuation(inputFrequencyRadPerSec, cutoffFrequencyRadPerSec); + + assertEquals(properHighPassAttenuation, filterAttenuation, 1e-2); + + cutoffFrequencyRadPerSec += 10.0; + } + } + + @Test + public void testLowPassAttenuationForSinusoidalInput() + { + double inputFrequencyRadPerSec = 10.0; + double cutoffFrequencyRadPerSec = inputFrequencyRadPerSec * 5.0; + double filterAttenuation = 1.0; + double properLowPassAttenuation; + + FirstOrderFilteredYoDouble lowPassFilteredYoVariable = new FirstOrderFilteredYoDouble("lowPass", + "", + cutoffFrequencyRadPerSec / (2.0 * Math.PI), + yoTime, + FirstOrderFilterType.LOW_PASS, + registry); + + while (filterAttenuation > 0.1 && cutoffFrequencyRadPerSec > 0.0) + { + lowPassFilteredYoVariable.setCutoffFrequencyHz(cutoffFrequencyRadPerSec / (2.0 * Math.PI)); + lowPassFilteredYoVariable.reset(); + filterAttenuation = computeSteadyStateFilteredOutputAmplitude(yoTime, DT, inputFrequencyRadPerSec, lowPassFilteredYoVariable); + + properLowPassAttenuation = computeProperLowPassAttenuation(inputFrequencyRadPerSec, cutoffFrequencyRadPerSec); + + assertEquals(properLowPassAttenuation, filterAttenuation, 1e-2); + + cutoffFrequencyRadPerSec -= 10.0; + } + } + + @Test + public void testBandPassAttenuationForSinusoidalInput() + { + double inputFrequencyRadPerSec = 10.0; + + double a = inputFrequencyRadPerSec / 5.0; + double b = inputFrequencyRadPerSec * 5.0; + + double filterAttenuation = 1.0; + double properBandPassAttenuation; + + FirstOrderBandPassFilteredYoDouble bandPassFilteredYoVariable = new FirstOrderBandPassFilteredYoDouble("sineWave", + "", + a, + b, + yoTime, + registry); + + while (filterAttenuation > 0.1 && a > 0.0 && b > 0.0) + { + bandPassFilteredYoVariable.setPassBand(a / (2.0 * Math.PI), b / (2.0 * Math.PI)); + bandPassFilteredYoVariable.reset(); + filterAttenuation = computeSteadyStateFilteredOutputAmplitude(yoTime, DT, inputFrequencyRadPerSec, bandPassFilteredYoVariable); + + properBandPassAttenuation = computeProperBandPassAttenuation(inputFrequencyRadPerSec, a, b); + + assertEquals(properBandPassAttenuation, filterAttenuation, 1e-2); + + a -= 10.0; + b -= 10.0; + } + } + + private double computeProperLowPassAttenuation(double inputFreq_RadPerSec, double cutoffFreq_RadPerSec) + { + double ret = cutoffFreq_RadPerSec / Math.sqrt(inputFreq_RadPerSec * inputFreq_RadPerSec + cutoffFreq_RadPerSec * cutoffFreq_RadPerSec); + return ret; + } + + private double computeProperHighPassAttenuation(double inputFreq_RadPerSec, double cutoffFreq_RadPerSec) + { + double ret = inputFreq_RadPerSec / Math.sqrt(inputFreq_RadPerSec * inputFreq_RadPerSec + cutoffFreq_RadPerSec * cutoffFreq_RadPerSec); + return ret; + } + + private double computeProperBandPassAttenuation(double inputFreq_RadPerSec, double minFreq_RadPerSec, double maxFreq_RadPerSec) + { + double highPass = computeProperHighPassAttenuation(inputFreq_RadPerSec, minFreq_RadPerSec); + double lowPass = computeProperLowPassAttenuation(inputFreq_RadPerSec, maxFreq_RadPerSec); + + double ret = highPass * lowPass; + return ret; + } + + private double computeSteadyStateFilteredOutputAmplitude(YoDouble yoTime, + double DT, + double inputFrequencyRadPerSec, + FirstOrderFilteredYoDouble filteredYoVariable) + { + double t; + double sineWaveInput; + + double filterOutput_oldest = 0.0; + double filterOutput_old = 0.0; + double filterOutput; + + double filterOutputPeak = 0.0; + double filterOutputPeakOld = 0.0; + double filterOutputPeakPercentChange = 100.0; + + boolean filterOutputHasReachedSteadyState = false; + + int i = 0; + + while (!filterOutputHasReachedSteadyState) + { + t = i * DT; + yoTime.set(t); + + sineWaveInput = Math.sin(inputFrequencyRadPerSec * t); + + filteredYoVariable.update(sineWaveInput); + + filterOutput = filteredYoVariable.getDoubleValue(); + + boolean filterOutputJustHitAPeak = filterOutput_old > filterOutput_oldest && filterOutput_old > filterOutput; + + if (filterOutputJustHitAPeak) + { + filterOutputPeak = filterOutput_old; + filterOutputPeakPercentChange = 100.0 * Math.abs((filterOutputPeak - filterOutputPeakOld) / filterOutputPeak); + + filterOutputPeakOld = filterOutputPeak; + + // System.out.println("Filter output peak :" + filterOutputPeak); + } + + filterOutputHasReachedSteadyState = filterOutputPeakPercentChange < 1e-6; + + filterOutput_oldest = filterOutput_old; + filterOutput_old = filterOutput; + i++; + } + + // System.out.println("Max Filter Output Percent Change: " + filterOutputPeakPercentChange); + + return filterOutputPeak; + } + + private double computeSteadyStateFilteredOutputAmplitude(YoDouble yoTime, + double DT, + double inputFrequencyRadPerSec, + FirstOrderBandPassFilteredYoDouble filteredYoVariable) + { + double t; + double sineWaveInput; + + double filterOutput_oldest = 0.0; + double filterOutput_old = 0.0; + double filterOutput; + + double filterOutputPeak = 0.0; + double filterOutputPeakOld = 0.0; + double filterOutputPeakPercentChange = 100.0; + + boolean filterOutputHasReachedSteadyState = false; + + int i = 0; + + while (!filterOutputHasReachedSteadyState) + { + t = i * DT; + yoTime.set(t); + + sineWaveInput = Math.sin(inputFrequencyRadPerSec * t); + + filteredYoVariable.update(sineWaveInput); + + filterOutput = filteredYoVariable.getDoubleValue(); + + boolean filterOutputJustHitAPeak = filterOutput_old > filterOutput_oldest && filterOutput_old > filterOutput; + + if (filterOutputJustHitAPeak) + { + filterOutputPeak = filterOutput_old; + filterOutputPeakPercentChange = 100.0 * Math.abs((filterOutputPeak - filterOutputPeakOld) / filterOutputPeak); + + filterOutputPeakOld = filterOutputPeak; + + // System.out.println("Filter output peak :" + filterOutputPeak); + } + + filterOutputHasReachedSteadyState = filterOutputPeakPercentChange < 1e-6; + + filterOutput_oldest = filterOutput_old; + filterOutput_old = filterOutput; + i++; + } + + // System.out.println("Max Filter Output Percent Change: " + filterOutputPeakPercentChange); + + return filterOutputPeak; + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBooleanTest.java b/src/test/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBooleanTest.java new file mode 100644 index 00000000..a19285a6 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/GlitchFilteredYoBooleanTest.java @@ -0,0 +1,152 @@ +package us.ihmc.yoVariables.filters; + + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +public class GlitchFilteredYoBooleanTest +{ + public static final int WINDOW_SIZE = 10; + private YoRegistry registry; + private YoBoolean yoVariableToFilter; + private GlitchFilteredYoBoolean filteredVariable; + + @BeforeEach + public void setUp() + { + registry = new YoRegistry("testRegistry"); + yoVariableToFilter = new YoBoolean("variableToFilter", registry); + filteredVariable = new GlitchFilteredYoBoolean("filteredVariable", registry, yoVariableToFilter, WINDOW_SIZE); + } + + @AfterEach + public void tearDown() + { + registry = null; + yoVariableToFilter = null; + filteredVariable = null; + } + + @Test + public void testConstructors_Set_Get() + { + GlitchFilteredYoBoolean number1 = new GlitchFilteredYoBoolean("stringInt", new YoRegistry("test"), WINDOW_SIZE); + GlitchFilteredYoBoolean number2 = new GlitchFilteredYoBoolean("stringYoVariableRegistryInt", registry, WINDOW_SIZE); + GlitchFilteredYoBoolean number3 = new GlitchFilteredYoBoolean("stringBooleanYoVariableInt", new YoRegistry("test"), yoVariableToFilter, WINDOW_SIZE); + GlitchFilteredYoBoolean number4 = new GlitchFilteredYoBoolean("stringYoVariableRegistryBooleanYoVariableInt", registry, + yoVariableToFilter, WINDOW_SIZE); + + GlitchFilteredYoBoolean array[] = { number1, number2, number3, number4 }; + + for (int i = 0; i < array.length; i++) + { + assertFalse(array[i].getBooleanValue()); + //assertFalse(array[i].getPreviousBooleanValue()); + + array[i].set(true); + assertTrue(array[i].getBooleanValue()); + //assertTrue(array[i].getPreviousBooleanValue()); + + array[i].set(false); + assertFalse(array[i].getBooleanValue()); + //assertFalse(array[i].getPreviousBooleanValue()); + } + } + + @Test + public void testUpdate() + { + int windowSize = 3; + filteredVariable.set(true); + filteredVariable.setWindowSize(windowSize); + int numberOfSets = windowSize * 20; + +// for (int i = 0; i < numberOfSets; i++) +// { +// if (i % 2 == 0) +// { +// filteredVariable.update(false); +// assertFalse(filteredVariable.getPreviousBooleanValue()); +// } +// else +// { +// filteredVariable.update(true); +// assertTrue(filteredVariable.getPreviousBooleanValue()); +// } +// assertTrue(filteredVariable.getBooleanValue()); +// } + + + int counter = 0; + for (int i = 0; i < (windowSize + 10); i++) + { + + filteredVariable.update(false); + counter++; + + //assertFalse(filteredVariable.getPreviousBooleanValue()); + + if (counter < windowSize ) + { + assertTrue(filteredVariable.getBooleanValue()); + } + else + { + assertFalse(filteredVariable.getBooleanValue()); + } + } + + filteredVariable = null; + try + { + filteredVariable.update(); + fail(); + } + catch (RuntimeException rte) + { + //do nothing + } + } + + @Test + public void testCounter() + { + int windowSize = 10; + filteredVariable.set(true); + filteredVariable.setWindowSize(windowSize); + + + for (int i = 0; i < ( (int)(windowSize/2.0)); i++) + { + filteredVariable.update(filteredVariable.getBooleanValue()); + assertEquals(filteredVariable.counter.getIntegerValue(), 0); + } + + } + + @Test + public void testFiltering() + { + yoVariableToFilter.set(true); + + for (int i = 0; i < WINDOW_SIZE / 2; i++) + { + filteredVariable.update(); + } + + assertFalse(yoVariableToFilter.getBooleanValue() == filteredVariable.getBooleanValue()); + + for (int i = 0; i < WINDOW_SIZE / 2; i++) + { + filteredVariable.update(); + } + + assertTrue(yoVariableToFilter.getBooleanValue() == filteredVariable.getBooleanValue()); + } +} \ No newline at end of file diff --git a/src/test/java/us/ihmc/yoVariables/filters/MovingAverageYoDoubleTest.java b/src/test/java/us/ihmc/yoVariables/filters/MovingAverageYoDoubleTest.java new file mode 100644 index 00000000..cefb7be0 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/MovingAverageYoDoubleTest.java @@ -0,0 +1,71 @@ +package us.ihmc.yoVariables.filters; + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class MovingAverageYoDoubleTest +{ + private final Random rng = new Random(); + + @Test + public void testBetaFilteredYoVariable() + { + int beta = 5000; + double pseudoNoise = 0; + + YoRegistry registry = new YoRegistry("testRegistry"); + YoDouble positionVariable = new YoDouble("positionVariable", registry); + MovingAverageYoDouble betaFilteredYoVariable = new MovingAverageYoDouble("betaFilteredYoVariable", registry, beta, positionVariable); + + positionVariable.set(10); + + for (int i = 0; i < 10000; i++) + { + if (i % 2 == 0) + { + pseudoNoise = rng.nextDouble(); + } + positionVariable.add(Math.pow(-1, i) * pseudoNoise); + betaFilteredYoVariable.update(); + } + + assertEquals(10, betaFilteredYoVariable.getDoubleValue(), 1); + } + + @Test + public void testTrueMovingAverage() + { + int beta = 10; + + YoRegistry registry = new YoRegistry("testRegistry"); + MovingAverageYoDouble betaFilteredYoVariable = new MovingAverageYoDouble("betaFilteredYoVariable", registry, beta); + + double epsilon = 1e-10; + + betaFilteredYoVariable.update(1.0); + assertEquals(1.0, betaFilteredYoVariable.getDoubleValue(), epsilon); + + betaFilteredYoVariable.update(2.0); + assertEquals(1.5, betaFilteredYoVariable.getDoubleValue(), epsilon); + + + betaFilteredYoVariable.update(3.0); + betaFilteredYoVariable.update(4.0); + betaFilteredYoVariable.update(5.0); + betaFilteredYoVariable.update(6.0); + betaFilteredYoVariable.update(7.0); + betaFilteredYoVariable.update(8.0); + betaFilteredYoVariable.update(9.0); + betaFilteredYoVariable.update(10.0); + + assertEquals(5.5, betaFilteredYoVariable.getDoubleValue(), epsilon); + } + + +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/RateLimitedYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/RateLimitedYoVariableTest.java new file mode 100644 index 00000000..f437422f --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/RateLimitedYoVariableTest.java @@ -0,0 +1,183 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.yoVariables.registry.YoRegistry; +import us.ihmc.yoVariables.variable.YoDouble; + +import static org.junit.jupiter.api.Assertions.*; + +public class RateLimitedYoVariableTest +{ + YoRegistry registry = new YoRegistry("registry"); + RateLimitedYoVariable rateLimitedYoVariable1, rateLimitedYoVariable2, rateLimitedYoVariable3, rateLimitedYoVariable4; + YoDouble maxRate2, maxRate4; + YoDouble position3, position4; + double maxRate1, maxRate3; + double dt1, dt2, dt3, dt4; + + @BeforeEach + public void setUp() + { + maxRate2 = new YoDouble("maxRate2", registry); + maxRate4 = new YoDouble("maxRate4", registry); + position3 = new YoDouble("position3", registry); + position4 = new YoDouble("position4", registry); + + maxRate1 = 10.0; + maxRate2.set(9.0); + maxRate3 = 11.0; + maxRate4.set(12.0); + + dt1 = 1.0; + dt2 = 1.0; + dt3 = 1.0; + dt4 = 1.0; + + position3.set(0.5); + position4.set(0.75); + + rateLimitedYoVariable1 = new RateLimitedYoVariable("rateLimitedYoVariable1", registry, maxRate1, dt1); + rateLimitedYoVariable2 = new RateLimitedYoVariable("rateLimitedYoVariable2", registry, maxRate2, dt2); + rateLimitedYoVariable3 = new RateLimitedYoVariable("rateLimitedYoVariable3", registry, maxRate3, position3, dt3); + rateLimitedYoVariable4 = new RateLimitedYoVariable("rateLimitedYoVariable4", registry, maxRate4, position4, dt4); + } + + @Test + public void testUpdate() + { + try + { + rateLimitedYoVariable3.update(); + rateLimitedYoVariable4.update(); + } + catch (Exception e) + { + fail(); + } + } + + @Test + public void testUpdateWithNullPointerException() + { + try + { + rateLimitedYoVariable1.update(); + rateLimitedYoVariable2.update(); + + fail("Did not throw NullPointerException."); + } + catch (Exception e) + { + + } + } + + @Test + public void testUpdateWithCurrentPositionParameter() + { + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition1 = 10.0 * Math.sin(angle); + rateLimitedYoVariable1.update(currentPosition1); + assertEquals(rateLimitedYoVariable1.getDoubleValue(), currentPosition1, 1E-13); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition2 = 7.0 * Math.sin(angle); + rateLimitedYoVariable2.update(currentPosition2); + assertEquals(rateLimitedYoVariable2.getDoubleValue(), currentPosition2, 1E-13); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition3 = 11.0 * Math.sin(angle); + rateLimitedYoVariable3.update(currentPosition3); + assertEquals(rateLimitedYoVariable3.getDoubleValue(), currentPosition3, 1E-13); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition4 = 12.0 * Math.sin(angle); + rateLimitedYoVariable4.update(currentPosition4); + assertEquals(rateLimitedYoVariable4.getDoubleValue(), currentPosition4, 1E-13); + } + } + + @Test + public void testUpdateWithCurrentPositionParameterExceedingMaxRate() + { + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition1 = 25.0 * Math.sin(angle); + double dSinTheta = Math.cos(angle); + double signOfSlope = Math.signum(dSinTheta) * 1.0; + + if (Math.abs(currentPosition1 - rateLimitedYoVariable1.getDoubleValue()) > maxRate1) + { + currentPosition1 = rateLimitedYoVariable1.getDoubleValue() + signOfSlope * maxRate1; + } + rateLimitedYoVariable1.update(currentPosition1); + assertEquals(rateLimitedYoVariable1.getDoubleValue(), currentPosition1, 1E-15); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition2 = 25.0 * Math.sin(angle); + double dSinTheta = Math.cos(angle); + double signOfSlope = Math.signum(dSinTheta) * 1.0; + + if (Math.abs(currentPosition2 - rateLimitedYoVariable2.getDoubleValue()) > maxRate2.getDoubleValue()) + { + currentPosition2 = rateLimitedYoVariable2.getDoubleValue() + signOfSlope * maxRate2.getDoubleValue(); + } + rateLimitedYoVariable2.update(currentPosition2); + assertEquals(rateLimitedYoVariable2.getDoubleValue(), currentPosition2, 1E-15); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition3 = 25.0 * Math.sin(angle); + double dSinTheta = Math.cos(angle); + double signOfSlope = Math.signum(dSinTheta) * 1.0; + + if (Math.abs(currentPosition3 - rateLimitedYoVariable3.getDoubleValue()) > maxRate3) + { + currentPosition3 = rateLimitedYoVariable3.getDoubleValue() + signOfSlope * maxRate3; + } + rateLimitedYoVariable3.update(currentPosition3); + assertEquals(rateLimitedYoVariable3.getDoubleValue(), currentPosition3, 1E-15); + } + + for (double angle = 0.0; angle < 3 * 6.28; angle += 1.0) + { + double currentPosition4 = 25.0 * Math.sin(angle); + double dSinTheta = Math.cos(angle); + double signOfSlope = Math.signum(dSinTheta) * 1.0; + + if (Math.abs(currentPosition4 - rateLimitedYoVariable4.getDoubleValue()) > maxRate4.getDoubleValue()) + { + currentPosition4 = rateLimitedYoVariable4.getDoubleValue() + signOfSlope * maxRate4.getDoubleValue(); + } + rateLimitedYoVariable4.update(currentPosition4); + assertEquals(rateLimitedYoVariable4.getDoubleValue(), currentPosition4, 1E-15); + } + } + + @Test + public void testUpdateWithMaxRateBeingNegative() + { + try + { + RateLimitedYoVariable rateLimitedYoVariableWithNegativeMaxRate = new RateLimitedYoVariable("rateLimitedYoVariableWithNegativeMaxRate", registry, -5.0, + 1.0); + rateLimitedYoVariableWithNegativeMaxRate.update(5.0); + } + catch (RuntimeException e) + { + assertEquals(e.getMessage(), "The maxRate parameter in the RateLimitedYoVariable cannot be negative."); + } + } +} \ No newline at end of file diff --git a/src/test/java/us/ihmc/yoVariables/filters/RunningAverageYoDoubleTest.java b/src/test/java/us/ihmc/yoVariables/filters/RunningAverageYoDoubleTest.java new file mode 100644 index 00000000..8b9909ee --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/RunningAverageYoDoubleTest.java @@ -0,0 +1,29 @@ +package us.ihmc.yoVariables.filters; + +import java.util.Random; + +import org.apache.commons.math3.stat.descriptive.moment.Mean; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class RunningAverageYoDoubleTest +{ + @Test + public void testAgainstApacheMean() + { + Random random = new Random(1); + RunningAverageYoDouble yoAverage = new RunningAverageYoDouble("", null); + Mean mean = new Mean(); + double next = -10.0; + + for (int i = 0; i < 1000; i++) + { + next += random.nextDouble(); + yoAverage.update(next); + mean.increment(next); + + Assertions.assertEquals(mean.getResult(), yoAverage.getValue()); + Assertions.assertEquals(mean.getN(), yoAverage.getSampleSize()); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDoubleTest.java b/src/test/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDoubleTest.java new file mode 100644 index 00000000..887a68dc --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/SecondOrderFilteredYoDoubleTest.java @@ -0,0 +1,68 @@ +package us.ihmc.yoVariables.filters; + +import org.junit.jupiter.api.Test; +import us.ihmc.yoVariables.registry.YoRegistry; + +import static org.junit.jupiter.api.Assertions.*; + +public class SecondOrderFilteredYoDoubleTest +{ + YoRegistry registry = new YoRegistry(getClass().getSimpleName()); + + @Test + public void testLowPassFilterCoefficients() + { + double dt = 0.001; + double dampingRatio = 1.0; + double naturalFrequencyInHz = 10.0; + double[] bAssert = {3947.8417604357433, 7895.6835208714865, 3947.8417604357433}; + double[] aAssert = {4255275.254047619, -7992104.316479129, 3752620.429473252}; + + SecondOrderFilteredYoDouble filteredYoVariable = new SecondOrderFilteredYoDouble("lowPass", registry, dt, naturalFrequencyInHz, dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType.LOW_PASS); + + double[] b = new double[3]; + double[] a = new double[3]; + filteredYoVariable.getFilterCoefficients(b, a); + assertArrayEquals(b, bAssert, 1e-8); + assertArrayEquals(a, aAssert, 1e-8); + } + + @Test + public void testNotchFilterCoefficients() + { + double dt = 0.001; + double dampingRatio = 1.0; + double naturalFrequencyInHz = 10.0; + double[] bAssert = {4003947.8417604356, -7992104.316479129, 4003947.8417604356}; + double[] aAssert = {4255275.254047619, -7992104.316479129, 3752620.429473252}; + + SecondOrderFilteredYoDouble filteredYoVariable = new SecondOrderFilteredYoDouble("notch", registry, dt, naturalFrequencyInHz, dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType.NOTCH); + + double[] b = new double[3]; + double[] a = new double[3]; + filteredYoVariable.getFilterCoefficients(b, a); + assertArrayEquals(b, bAssert, 1e-8); + assertArrayEquals(a, aAssert, 1e-8); + } + + @Test + public void testHighPassFilterCoefficients() + { + double dt = 0.001; + double dampingRatio = 1.0; + double naturalFrequencyInHz = 10.0; + double[] bAssert = {4000000.0, -8000000.0, 4000000.0}; + double[] aAssert = {4255275.254047619, -7992104.316479129, 3752620.429473252}; + + SecondOrderFilteredYoDouble filteredYoVariable = new SecondOrderFilteredYoDouble("highPass", registry, dt, naturalFrequencyInHz, dampingRatio, + SecondOrderFilteredYoDouble.SecondOrderFilterType.HIGH_PASS); + + double[] b = new double[3]; + double[] a = new double[3]; + filteredYoVariable.getFilterCoefficients(b, a); + assertArrayEquals(b, bAssert, 1e-8); + assertArrayEquals(a, aAssert, 1e-8); + } +} diff --git a/src/test/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariableTest.java b/src/test/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariableTest.java new file mode 100644 index 00000000..a62aa785 --- /dev/null +++ b/src/test/java/us/ihmc/yoVariables/filters/SimpleMovingAverageFilteredYoVariableTest.java @@ -0,0 +1,39 @@ +package us.ihmc.yoVariables.filters; + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import us.ihmc.commons.RandomNumbers; +import us.ihmc.yoVariables.registry.YoRegistry; + +import static org.junit.jupiter.api.Assertions.*; + +public class SimpleMovingAverageFilteredYoVariableTest +{ + @Test + public void testWithFixedSizeDoubleArrays() + { + for (int i = 0; i < 100; i++) + { + YoRegistry registry = new YoRegistry("Blop"); + Random random = new Random(6541654L); + int windowSize = RandomNumbers.nextInt(random, 1, 1000); + SimpleMovingAverageFilteredYoVariable sma = new SimpleMovingAverageFilteredYoVariable("tested", windowSize, registry); + double amplitude = 100.0; + double[] randomArray = RandomNumbers.nextDoubleArray(random, windowSize, amplitude); + double expected = 0.0; + for (double val : randomArray) + expected += val / windowSize; + + for (int j = 0; j < randomArray.length; j++) + { + assertFalse(sma.getHasBufferWindowFilled()); + sma.update(randomArray[j]); + } + + assertTrue(sma.getHasBufferWindowFilled()); + assertEquals(expected, sma.getDoubleValue(), 1.0e-10); + } + } +} diff --git a/src/test/java/us/ihmc/yoVariables/math/YoMatrixTest.java b/src/test/java/us/ihmc/yoVariables/math/YoMatrixTest.java index a744b96a..c4a6df2f 100644 --- a/src/test/java/us/ihmc/yoVariables/math/YoMatrixTest.java +++ b/src/test/java/us/ihmc/yoVariables/math/YoMatrixTest.java @@ -1,5 +1,6 @@ package us.ihmc.yoVariables.math; +import org.ejml.EjmlUnitTests; import org.ejml.data.DMatrixRMaj; import org.ejml.dense.row.CommonOps_DDRM; import org.ejml.dense.row.RandomMatrices_DDRM; @@ -47,6 +48,39 @@ public void testSimpleYoMatrixRefactorExample() assertEquals(smallerRowSize * smallerColumnSize, matrix.getNumElements()); } + @Test + public void testSimpleYoMatrixExample() + { + int maxNumberOfRows = 4; + int maxNumberOfColumns = 8; + YoRegistry registry = new YoRegistry("testRegistry"); + us.ihmc.yoVariables.filters.YoMatrix yoMatrix = new us.ihmc.yoVariables.filters.YoMatrix("testMatrix", maxNumberOfRows, maxNumberOfColumns, registry); + assertEquals(maxNumberOfRows, yoMatrix.getNumRows()); + assertEquals(maxNumberOfColumns, yoMatrix.getNumCols()); + + DMatrixRMaj denseMatrix = new DMatrixRMaj(maxNumberOfRows, maxNumberOfColumns); + yoMatrix.reshape(maxNumberOfRows, maxNumberOfColumns); + yoMatrix.zero(); + yoMatrix.get(denseMatrix); + + DMatrixRMaj zeroMatrix = new DMatrixRMaj(maxNumberOfRows, maxNumberOfColumns); + EjmlUnitTests.assertEquals(zeroMatrix, denseMatrix, 1e-10); + + Random random = new Random(1984L); + + DMatrixRMaj randomMatrix = RandomMatrices_DDRM.rectangle(maxNumberOfRows, maxNumberOfColumns, random); + yoMatrix.set(randomMatrix); + + DMatrixRMaj checkMatrix = new DMatrixRMaj(maxNumberOfRows, maxNumberOfColumns); + yoMatrix.get(checkMatrix); + + EjmlUnitTests.assertEquals(randomMatrix, checkMatrix, 1e-10); + + assertEquals(registry.findVariable(us.ihmc.yoVariables.filters.YoMatrix.getFieldName("testMatrix", 0, 0)).getValueAsDouble(), checkMatrix.get(0, 0), 1e-10); + } + + + @Test public void testConstructorsWithNamesAndDescriptions() { @@ -278,6 +312,76 @@ public void testScale() } } + + @Test + public void testYoMatrixSetTooBig() + { + int maxNumberOfRows = 4; + int maxNumberOfColumns = 8; + String name = "testMatrix"; + YoRegistry registry = new YoRegistry("testRegistry"); + us.ihmc.yoVariables.filters.YoMatrix yoMatrix = new us.ihmc.yoVariables.filters.YoMatrix(name, maxNumberOfRows, maxNumberOfColumns, registry); + + DMatrixRMaj tooBigMatrix = new DMatrixRMaj(maxNumberOfRows + 1, maxNumberOfColumns); + + try + { + yoMatrix.set(tooBigMatrix); + fail("Too Big"); + } + catch (RuntimeException e) + { + } + + tooBigMatrix = new DMatrixRMaj(maxNumberOfRows, maxNumberOfColumns + 1); + + try + { + yoMatrix.set(tooBigMatrix); + fail("Too Big"); + } + catch (RuntimeException e) + { + } + + // Test a 0 X Big Matrix + DMatrixRMaj okMatrix = new DMatrixRMaj(0, maxNumberOfColumns + 10); + yoMatrix.set(okMatrix); + assertMatrixYoVariablesAreNaN(name, maxNumberOfRows, maxNumberOfColumns, registry); + + DMatrixRMaj checkMatrix = new DMatrixRMaj(1, 1); + yoMatrix.getAndReshape(checkMatrix); + + assertEquals(0, checkMatrix.getNumRows()); + assertEquals(maxNumberOfColumns + 10, checkMatrix.getNumCols()); + + // Test a Big X 0 Matrix + + okMatrix = new DMatrixRMaj(maxNumberOfRows + 10, 0); + yoMatrix.set(okMatrix); + assertMatrixYoVariablesAreNaN(name, maxNumberOfRows, maxNumberOfColumns, registry); + + checkMatrix = new DMatrixRMaj(1, 1); + yoMatrix.getAndReshape(checkMatrix); + + assertEquals(maxNumberOfRows + 10, checkMatrix.getNumRows()); + assertEquals(0, checkMatrix.getNumCols()); + + } + + + private static void assertMatrixYoVariablesAreNaN(String name, int maxNumberOfRows, int maxNumberOfColumns, YoRegistry registry) + { + for (int row = 0; row < maxNumberOfRows; row++) + { + for (int column = 0; column < maxNumberOfColumns; column++) + { + YoDouble variable = (YoDouble) registry.findVariable(us.ihmc.yoVariables.filters.YoMatrix.getFieldName(name, row, column)); + assertTrue(Double.isNaN(variable.getDoubleValue())); + } + } + } + @Test public void testScaleFailureCases() {