Clean up LinearDigitalFilter class (#782)

* Renamed LinearDigitalFilter to LinearFilter
* Filter base class removed since it wasn't useful
* C++: std::shared_ptr<> replaced with double parameter
This commit is contained in:
Tyler Veness
2019-06-28 13:35:57 -07:00
committed by Peter Johnson
parent 311e2de4c1
commit 30e936837c
22 changed files with 771 additions and 960 deletions

View File

@@ -1,97 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj;
import java.util.Arrays;
import java.util.Collection;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import edu.wpi.first.wpilibj.fixtures.FilterNoiseFixture;
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
import edu.wpi.first.wpilibj.test.TestBench;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class FilterNoiseTest extends AbstractComsSetup {
private static final Logger logger = Logger.getLogger(FilterNoiseTest.class.getName());
private static FilterNoiseFixture<?> me = null;
@Override
protected Logger getClassLogger() {
return logger;
}
/**
* Constructs the FilterNoiseTest.
*
* @param mef The fixture under test.
*/
public FilterNoiseTest(FilterNoiseFixture<?> mef) {
logger.fine("Constructor with: " + mef.getType());
if (me != null && !me.equals(mef)) {
me.teardown();
}
me = mef;
}
@Parameters(name = "{index}: {0}")
public static Collection<FilterNoiseFixture<?>[]> generateData() {
return Arrays.asList(new FilterNoiseFixture<?>[][]{
{TestBench.getInstance().getSinglePoleIIRNoiseFixture()},
{TestBench.getInstance().getMovAvgNoiseFixture()}});
}
@Before
public void setUp() {
me.setup();
}
@After
public void tearDown() throws Exception {
me.reset();
}
@AfterClass
public static void tearDownAfterClass() {
// Clean up the fixture after the test
me.teardown();
me = null;
}
/**
* Test if the filter reduces the noise produced by a signal generator.
*/
@Test
public void testNoiseReduce() {
double noiseGenError = 0.0;
double filterError = 0.0;
FilterNoiseFixture.NoiseGenerator noise = me.getNoiseGenerator();
noise.reset();
for (double t = 0; t < TestBench.kFilterTime; t += TestBench.kFilterStep) {
final double theoryData = noise.getData(t);
filterError += Math.abs(me.getFilter().pidGet() - theoryData);
noiseGenError += Math.abs(noise.get() - theoryData);
}
assertTrue(me.getType() + " should have reduced noise accumulation from " + noiseGenError
+ " but failed. The filter error was " + filterError, noiseGenError > filterError);
}
}

View File

@@ -1,96 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj;
import java.util.Arrays;
import java.util.Collection;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import edu.wpi.first.wpilibj.fixtures.FilterOutputFixture;
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
import edu.wpi.first.wpilibj.test.TestBench;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class FilterOutputTest extends AbstractComsSetup {
private static final Logger logger = Logger.getLogger(FilterOutputTest.class.getName());
private double m_expectedOutput;
private static FilterOutputFixture<?> me = null;
@Override
protected Logger getClassLogger() {
return logger;
}
/**
* Constructs a filter output test.
*
* @param mef The fixture under test.
*/
public FilterOutputTest(FilterOutputFixture<?> mef) {
logger.fine("Constructor with: " + mef.getType());
if (me != null && !me.equals(mef)) {
me.teardown();
}
me = mef;
m_expectedOutput = me.getExpectedOutput();
}
@Parameters(name = "{index}: {0}")
public static Collection<FilterOutputFixture<?>[]> generateData() {
return Arrays.asList(new FilterOutputFixture<?>[][]{
{TestBench.getInstance().getSinglePoleIIROutputFixture()},
{TestBench.getInstance().getHighPassOutputFixture()},
{TestBench.getInstance().getMovAvgOutputFixture()},
{TestBench.getInstance().getPulseFixture()}});
}
@Before
public void setUp() {
me.setup();
}
@After
public void tearDown() throws Exception {
me.reset();
}
@AfterClass
public static void tearDownAfterClass() {
// Clean up the fixture after the test
me.teardown();
me = null;
}
/**
* Test if the filter produces consistent output for a given data set.
*/
@Test
public void testOutput() {
me.reset();
double filterOutput = 0.0;
for (double t = 0.0; t < TestBench.kFilterTime; t += TestBench.kFilterStep) {
filterOutput = me.getFilter().pidGet();
}
assertEquals(me.getType() + " output was incorrect.", m_expectedOutput, filterOutput, 0.00005);
}
}

View File

@@ -21,9 +21,8 @@ import edu.wpi.first.wpilibj.test.AbstractTestSuite;
@SuiteClasses({AnalogCrossConnectTest.class, AnalogPotentiometerTest.class,
BuiltInAccelerometerTest.class, ConstantsPortsTest.class, CounterTest.class,
DigitalGlitchFilterTest.class, DIOCrossConnectTest.class, DriveTest.class,
DriverStationTest.class, EncoderTest.class, FilterNoiseTest.class, FilterOutputTest.class,
GyroTest.class, MotorEncoderTest.class, MotorInvertingTest.class, PCMTest.class, PDPTest.class,
PIDTest.class, PreferencesTest.class, RelayCrossConnectTest.class, SampleTest.class,
TimerTest.class})
DriverStationTest.class, EncoderTest.class, GyroTest.class, MotorEncoderTest.class,
MotorInvertingTest.class, PCMTest.class, PDPTest.class, PIDTest.class, PreferencesTest.class,
RelayCrossConnectTest.class, SampleTest.class, TimerTest.class})
public class WpiLibJTestSuite extends AbstractTestSuite {
}

View File

@@ -1,159 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.fixtures;
import java.lang.reflect.ParameterizedType;
import java.util.Random;
import java.util.logging.Logger;
import edu.wpi.first.wpilibj.PIDSource;
import edu.wpi.first.wpilibj.PIDSourceType;
import edu.wpi.first.wpilibj.test.TestBench;
/**
* Represents a physically connected Motor and Encoder to allow for unit tests on these different
* pairs<br> Designed to allow the user to easily setup and tear down the fixture to allow for
* reuse. This class should be explicitly instantiated in the TestBed class to allow any test to
* access this fixture. This allows tests to be mailable so that you can easily reconfigure the
* physical testbed without breaking the tests.
*/
public abstract class FilterNoiseFixture<T extends PIDSource> implements ITestFixture {
private static final Logger logger = Logger.getLogger(FilterNoiseFixture.class.getName());
private boolean m_initialized = false;
private boolean m_tornDown = false;
protected T m_filter;
private NoiseGenerator m_data;
/**
* Where the implementer of this class should pass the filter constructor.
*/
protected abstract T giveFilter(PIDSource source);
private void initialize() {
synchronized (this) {
if (!m_initialized) {
m_initialized = true; // This ensures it is only initialized once
m_data = new NoiseGenerator(TestBench.kStdDev) {
@Override
@SuppressWarnings("ParameterName")
public double getData(double t) {
return 100.0 * Math.sin(2.0 * Math.PI * t);
}
};
m_filter = giveFilter(m_data);
}
}
}
@Override
public boolean setup() {
initialize();
return true;
}
/**
* Gets the filter for this Object.
*
* @return the filter this object refers too
*/
public T getFilter() {
initialize();
return m_filter;
}
/**
* Gets the noise generator for this object.
*
* @return the noise generator that this object refers too
*/
public NoiseGenerator getNoiseGenerator() {
initialize();
return m_data;
}
/**
* Retrieves the name of the filter that this object refers to.
*
* @return The simple name of the filter {@link Class#getSimpleName()}
*/
public String getType() {
initialize();
return m_filter.getClass().getSimpleName();
}
// test here?
@Override
public boolean reset() {
return true;
}
@Override
public boolean teardown() {
return true;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder("FilterNoiseFixture<");
// Get the generic type as a class
@SuppressWarnings("unchecked")
Class<T> class1 =
(Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
string.append(class1.getSimpleName());
string.append(">");
return string.toString();
}
/**
* Adds Gaussian white noise to a function returning data. The noise will have the standard
* deviation provided in the constructor.
*/
public abstract class NoiseGenerator implements PIDSource {
private double m_noise = 0.0;
// Make sure first call to pidGet() uses count == 0
private double m_count = -TestBench.kFilterStep;
private double m_stdDev;
private Random m_gen = new Random();
NoiseGenerator(double stdDev) {
m_stdDev = stdDev;
}
@SuppressWarnings("ParameterName")
public abstract double getData(double t);
@Override
public void setPIDSourceType(PIDSourceType pidSource) {
}
@Override
public PIDSourceType getPIDSourceType() {
return PIDSourceType.kDisplacement;
}
public double get() {
return getData(m_count) + m_noise;
}
@Override
public double pidGet() {
m_noise = m_gen.nextGaussian() * m_stdDev;
m_count += TestBench.kFilterStep;
return getData(m_count) + m_noise;
}
public void reset() {
m_count = -TestBench.kFilterStep;
}
}
}

View File

@@ -1,159 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.wpilibj.fixtures;
import java.lang.reflect.ParameterizedType;
import java.util.function.DoubleFunction;
import java.util.logging.Logger;
import edu.wpi.first.wpilibj.PIDSource;
import edu.wpi.first.wpilibj.PIDSourceType;
import edu.wpi.first.wpilibj.test.TestBench;
/**
* Represents a filter to allow for unit tests on them<br> Designed to allow the user to easily
* setup and tear down the fixture to allow for reuse. This class should be explicitly instantiated
* in the TestBed class to allow any test to access this fixture. This allows tests to be mailable
* so that you can easily reconfigure the physical testbed without breaking the tests.
*/
public abstract class FilterOutputFixture<T extends PIDSource> implements ITestFixture {
private static final Logger logger = Logger.getLogger(FilterOutputFixture.class.getName());
private boolean m_initialized = false;
private boolean m_tornDown = false;
protected T m_filter;
protected DataWrapper m_data;
private double m_expectedOutput;
public FilterOutputFixture(double expectedOutput) {
m_expectedOutput = expectedOutput;
}
/**
* Get expected output of fixture.
*/
public double getExpectedOutput() {
return m_expectedOutput;
}
public static DoubleFunction<Double> getData = new DoubleFunction<Double>() {
@Override
@SuppressWarnings("ParameterName")
public Double apply(double t) {
return 100.0 * Math.sin(2.0 * Math.PI * t) + 20.0 * Math.cos(50.0 * Math.PI * t);
}
};
public static DoubleFunction<Double> getPulseData = new DoubleFunction<Double>() {
@Override
@SuppressWarnings("ParameterName")
public Double apply(double t) {
if (Math.abs(t - 1.0) < 0.001) {
return 1.0;
} else {
return 0.0;
}
}
};
/**
* Where the implementer of this class should pass the filter constructor.
*/
protected abstract T giveFilter();
private void initialize() {
synchronized (this) {
if (!m_initialized) {
m_initialized = true; // This ensures it is only initialized once
m_filter = giveFilter();
}
}
}
@Override
public boolean setup() {
initialize();
return true;
}
/**
* Gets the filter for this Object.
*
* @return the filter this object refers too
*/
public T getFilter() {
initialize();
return m_filter;
}
/**
* Retrieves the name of the filter that this object refers to.
*
* @return The simple name of the filter {@link Class#getSimpleName()}
*/
public String getType() {
initialize();
return m_filter.getClass().getSimpleName();
}
@Override
public boolean reset() {
m_data.reset();
return true;
}
@Override
public boolean teardown() {
return true;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder("FilterOutputFixture<");
// Get the generic type as a class
@SuppressWarnings("unchecked")
Class<T> class1 =
(Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
string.append(class1.getSimpleName());
string.append(">");
return string.toString();
}
public class DataWrapper implements PIDSource {
// Make sure first call to pidGet() uses count == 0
private double m_count = -TestBench.kFilterStep;
private DoubleFunction<Double> m_func;
public DataWrapper(DoubleFunction<Double> func) {
m_func = func;
}
@Override
public void setPIDSourceType(PIDSourceType pidSource) {
}
@Override
public PIDSourceType getPIDSourceType() {
return PIDSourceType.kDisplacement;
}
@Override
public double pidGet() {
m_count += TestBench.kFilterStep;
return m_func.apply(m_count);
}
public void reset() {
m_count = -TestBench.kFilterStep;
}
}
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2018 FIRST. All Rights Reserved. */
/* Copyright (c) 2008-2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -18,16 +18,12 @@ import edu.wpi.first.wpilibj.AnalogInput;
import edu.wpi.first.wpilibj.AnalogOutput;
import edu.wpi.first.wpilibj.DigitalInput;
import edu.wpi.first.wpilibj.Jaguar;
import edu.wpi.first.wpilibj.PIDSource;
import edu.wpi.first.wpilibj.Relay;
import edu.wpi.first.wpilibj.Servo;
import edu.wpi.first.wpilibj.Talon;
import edu.wpi.first.wpilibj.Victor;
import edu.wpi.first.wpilibj.filters.LinearDigitalFilter;
import edu.wpi.first.wpilibj.fixtures.AnalogCrossConnectFixture;
import edu.wpi.first.wpilibj.fixtures.DIOCrossConnectFixture;
import edu.wpi.first.wpilibj.fixtures.FilterNoiseFixture;
import edu.wpi.first.wpilibj.fixtures.FilterOutputFixture;
import edu.wpi.first.wpilibj.fixtures.MotorEncoderFixture;
import edu.wpi.first.wpilibj.fixtures.RelayCrossConnectFixture;
import edu.wpi.first.wpilibj.fixtures.TiltPanCameraFixture;
@@ -65,17 +61,6 @@ public final class TestBench {
public static final int DIOCrossConnectA2 = 7;
public static final int DIOCrossConnectA1 = 6;
// Filter constants
public static final double kStdDev = 10.0;
public static final double kFilterStep = 0.005;
public static final double kFilterTime = 2.0;
public static final double kSinglePoleIIRTimeConstant = 0.015915;
public static final double kSinglePoleIIRExpectedOutput = -3.2172003;
public static final double kHighPassTimeConstant = 0.006631;
public static final double kHighPassExpectedOutput = 10.074717;
public static final int kMovAvgTaps = 6;
public static final double kMovAvgExpectedOutput = -10.191644;
/**
* The Singleton instance of the Test Bench.
*/
@@ -343,103 +328,6 @@ public final class TestBench {
return encoderPortPairs;
}
/**
* Constructs a new set of objects representing a single-pole IIR filter with a noisy data source.
*
* @return a single-pole IIR filter with a noisy data source
*/
public FilterNoiseFixture<LinearDigitalFilter> getSinglePoleIIRNoiseFixture() {
return new FilterNoiseFixture<LinearDigitalFilter>() {
@Override
protected LinearDigitalFilter giveFilter(PIDSource source) {
return LinearDigitalFilter.singlePoleIIR(source,
kSinglePoleIIRTimeConstant,
kFilterStep);
}
};
}
/**
* Constructs a new set of objects representing a moving average filter with a noisy data source
* using a linear digital filter.
*
* @return a moving average filter with a noisy data source
*/
public FilterNoiseFixture<LinearDigitalFilter> getMovAvgNoiseFixture() {
return new FilterNoiseFixture<LinearDigitalFilter>() {
@Override
protected LinearDigitalFilter giveFilter(PIDSource source) {
return LinearDigitalFilter.movingAverage(source, kMovAvgTaps);
}
};
}
/**
* Constructs a new set of objects representing a single-pole IIR filter with a repeatable data
* source.
*
* @return a single-pole IIR filter with a repeatable data source
*/
public FilterOutputFixture<LinearDigitalFilter> getSinglePoleIIROutputFixture() {
return new FilterOutputFixture<LinearDigitalFilter>(kSinglePoleIIRExpectedOutput) {
@Override
protected LinearDigitalFilter giveFilter() {
m_data = new DataWrapper(getData);
return LinearDigitalFilter.singlePoleIIR(m_data,
kSinglePoleIIRTimeConstant,
kFilterStep);
}
};
}
/**
* Constructs a new set of objects representing a high-pass filter with a repeatable data source.
*
* @return a high-pass filter with a repeatable data source
*/
public FilterOutputFixture<LinearDigitalFilter> getHighPassOutputFixture() {
return new FilterOutputFixture<LinearDigitalFilter>(kHighPassExpectedOutput) {
@Override
protected LinearDigitalFilter giveFilter() {
m_data = new DataWrapper(getData);
return LinearDigitalFilter.highPass(m_data, kHighPassTimeConstant,
kFilterStep);
}
};
}
/**
* Constructs a new set of objects representing a moving average filter with a repeatable data
* source using a linear digital filter.
*
* @return a moving average filter with a repeatable data source
*/
public FilterOutputFixture<LinearDigitalFilter> getMovAvgOutputFixture() {
return new FilterOutputFixture<LinearDigitalFilter>(kMovAvgExpectedOutput) {
@Override
protected LinearDigitalFilter giveFilter() {
m_data = new DataWrapper(getData);
return LinearDigitalFilter.movingAverage(m_data, kMovAvgTaps);
}
};
}
/**
* Constructs a new set of objects representing a moving average filter with a repeatable data
* source using a linear digital filter.
*
* @return a moving average filter with a repeatable data source
*/
public FilterOutputFixture<LinearDigitalFilter> getPulseFixture() {
return new FilterOutputFixture<LinearDigitalFilter>(0.0) {
@Override
protected LinearDigitalFilter giveFilter() {
m_data = new DataWrapper(getPulseData);
return LinearDigitalFilter.movingAverage(m_data, kMovAvgTaps);
}
};
}
/**
* Gets the singleton of the TestBench. If the TestBench is not already allocated in constructs an
* new instance of it. Otherwise it returns the existing instance.