Java Programming Notes # 2362
- Preface
- Preview
- Experimental Results
- Discussion and
Sample Code - Run the Program
- Summary
- References
- Complete Program
Listings
Preface
DSP and adaptive filtering
Digital Signal Processing (DSP)
is showing up in everything from cell phones to hearing aids and rock concerts.
A
particularly interesting branch of DSP is adaptive filtering. This is a
scenario where the characteristics of the digital processor change with time,
circumstances, or both.
Seventh
in a series
This is the seventh lesson in a series designed to teach you
about adaptive filtering in Java. The first lesson, entitled
Adaptive
Filtering in Java, Getting Started, introduced you to the topic of adaptively designing a convolution
filter using an
LMS
adaptive algorithm.
A general-purpose adaptive engine
The third lesson in the series, entitled
A
General-Purpose LMS Adaptive Engine in Java, presented and explained a general-purpose
LMS adaptive engine
written in Java. That engine can be used to solve a wide variety of
adaptive problems.
Adaptive noise cancellation
The previous lesson entitled
Adaptive Noise Cancellation using Java showed you how to accomplish the
third item, Noise Cancellation, in
the following list of common applications of adaptive filtering:
- System Identification
- Inverse System Identification
- Noise Cancellation
- Prediction
Adaptive prediction
This lesson presents and explains a program named Adapt09, which
demonstrates the use of adaptive filtering for the prediction of future values
in a time series.
Viewing tip
You may find it useful to open another copy of this lesson in a
separate browser window. That will make it easier for you to
scroll back
and forth among the different listings and figures while you are
reading
about them.
Supplementary material
I recommend that you also study the other lessons in my extensive
collection of online Java tutorials. You will find those lessons
published
at Gamelan.com.
However, as of the date of this writing, Gamelan doesn’t maintain a
consolidated index of my Java tutorial lessons, and sometimes they are
difficult to locate there. You will find a consolidated index at www.DickBaldwin.com.
In preparation for understanding the material in this lesson,
I recommend that you also study the lessons identified in the
References section of this document.
Preview
What is adaptive prediction?
Quite simply, adaptive prediction is the ability to adaptively design a
convolution filter that can be applied to a time series in order to predict
future values of the time series. I will let you use your own imagination
(or perhaps research on the web) to come up with various scenarios where this may
be useful.
The program named Adapt09
The purpose of this program is to illustrate an adaptive prediction filter.
The user can experiment with adaptive prediction by making changes to the
following program parameters and observing the printed and graphic results:
- feedbackGain
- adaptiveFilterLength
- predictionDistance
- signalScale
- noiseScale
- bandpassFilterLength (and hence bandwidth)
A block diagram
See the following URL for a brief description and a block diagram of an
adaptive prediction filter.
http://www.mathworks.com/access/helpdesk/help/toolbox/filterdesign/
adaptiv8.html#5576
I will refer to this block diagram later when discussing this program.
Classes required
This program requires the following classes:
- Adapt09.class
- AdaptEngine02.class
- AdaptiveResult.class
- ForwardRealToComplex01.class
- PlotALot01.class
- PlotALot03.class
- PlotALot05.class
The source code for the class named Adapt09 is presented in Listing 14
near the end of the lesson. The source code for the other classes in the
above list can be found in the lessons referred to in the
References section of this lesson.
A general-purpose adaptive engine
This program uses the adaptive engine named AdaptEngine02 to
adaptively develop a convolution prediction filter. The adaptive engine is
represented by the shaded portion of the
block diagram.
One of the inputs to the adaptive engine is the current sample of the sum of
signal plus noise, shown as d(k) in the
block diagram. This is the target of the prediction process.
The other input to the adaptive engine is an historical sample of signal plus
noise shown as x(k) in the
block diagram.
The adaptive engine develops a convolution filter (shown as Adaptive
Filter in the
block diagram) that attempts to use historical signal plus noise samples
to predict the value of the current sample of signal plus noise (target).
Thus, the program attempts to adaptively develop a convolution filter that can
operate on a time series to predict a future value of the time series.
The distance in time to the future value being predicted is a user input
parameter.
The iterative process
The program performs 9890 iterations during which the convolution filter
coefficients are adaptively updated. Then the adaptive update process is
disabled. The program then performs 9890 more iterations during which time
the quality of the prediction is measured and reported.
The prediction quality
The quality of the prediction is measured by:
- Computing the
Root-Mean-Square (RMS) value of the target (shown as d(k) in the
block diagram) averaged over 9890 samples. - Computing the RMS value of the prediction error (shown as e(k) in the
block diagram) averaged over 9890 samples. - Computing the percentage of the RMS target value represented by the RMS
error value.
Signal and noise characteristics
The signal consists of a pure tone at a frequency that is one eighth of the
sampling frequency (eight samples per cycle). The peak-to-peak
value of the signal prior to scaling is 2.
The noise consists of white noise produced by a random number generator with
a uniform distribution between -1.0 and 1.0 prior to scaling.
The scaled signal is added to the scaled white noise.
Conditioning the signal plus noise
The sum of signal
plus white noise can be passed through a variable-width band pass filter with a center frequency
equal to one-eighth of the sampling frequency.
(The band pass filter is not shown in the
block diagram. Consider it to be to the left of the
block
diagram with s(k) as its output.)
The center frequency of the band pass filter is the same as the frequency of
the signal. Thus, the signal plus noise consists of a pure tone surrounded
by noise for which the noise bandwidth can be controlled by the user. The
frequency band for the noise is centered on the signal frequency. The user
controls the signal level and the noise level and hence the signal-to-noise
ratio.
The band pass filter
The band pass filter consists of a convolution filter in the form of a
truncated sinusoid with a frequency of one-eighth of the sampling frequency.
The user specifies the length of the convolution filter, (up to a limit of
128 coefficients), thereby specifying the bandwidth of the filter. The
bandwidth of the filter is
roughly proportional to the reciprocal of the length of the convolution filter.
The shape of the amplitude response of the pass band for the filter is roughly
that of a sin(x)/x function.
The graphic program output
The program produces three graphs in a vertical stack on the screen as shown
in Figure 1.
Figure 1 |
The three graphs display the following information in order from top to
bottom:
- The band pass convolution filter used to filter the raw signal plus
noise (see the left panel in Figure 12 as an
example). - The frequency response of the band pass filter used to filter the raw
signal plus noise (see the right panel in Figure 12
as an example). The frequency response extends from a frequency of
zero to one-half the sampling frequency (the Nyquist folding frequency). - Four traces of adaptive time-series data (see
Figure 3 for an example).
Multiple stacked pages
The bottom graph consists of
multiple pages stacked on top of one another. (There are 230 samples
plotted in each trace on each page.) You must physically move the pages on the top
of the stack to view the pages further down. The pages on the top of the stack
represent the results produced early in the adaptive process while those further
down represent the results produced later in the adaptive process.
The time series
The four time
series that are plotted in the bottom graph are, from top to bottom (in the colors indicated):
- (Black)
The historical signal plus noise input to the adaptive prediction filter, shown
as x(k) in the
block diagram. - (Red)
The signal plus noise target for the adaptive prediction filter shown as d(k)
in the
block diagram. - (Blue) The
output from the adaptive prediction filter shown as y(k) in the
block diagram. - (Green) The prediction error, shown as e(k) in the
block diagram. The prediction error is the
difference between the red target and the blue output from the adaptive prediction filter. Ideally,
the prediction error approaches zero as the process
converges to a solution.
The printed program output
In addition to the graphic output, the program displays the quality of the
prediction process and the program parameters on the command-line screen as shown in Figure 2.
RMS target: 6.410217370961125 RMS error: 1.4675827349708308 Percent error: 22.894430095601994 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 26 predictionDistance: 1 signalScale: 0.0 noiseScale: 87.5 bpFilterLength: 128 rmsAveragingLength: 9890 Figure 2 |
User input
User input is provided by way of command-line
parameters. If no command-line parameters are provided, default values are used
for the program parameters.
The command-line parameters in order are:
- double feedbackGain: The gain used in the adaptive feedback loop. The default value is
0.00001. - int adaptiveFilterLength: The default value is 26.
- int predictionDistance: The distance in the future, (measured in samples), that the
adaptive process attempts to predict the value of signal plus noise (target). The
default value is 1 sample. - double signalScale: The default value is
0. Adjust this and the following parameter to adjust the signal-to-noise ratio, and
also to cause the time-series plots to be in good plotting range. - double noiseScale: The default value is 87.5.
- int bandpassFilterLength: The length of
the convolution filter that is applied to the raw signal plus noise. This value
must be less than or equal to 128. If it is greater than 128, it is automatically
set to 128.
Program testing
The program was tested using J2SE 5.0 and WinXP. J2SE 5.0 or later is required.
Experimental Results
Before getting into the program details, I’m going to show you some
experimental results.
The bottom line at the beginning
As you will see from these experimental results, the ability to predict future values in a time series tends
to vary inversely with the bandwidth of the time series. Future values of
narrow-band time series can be predicted reasonably well. Future values of
wide-band time series cannot be predicted well at all.
I will begin by showing you results for the two extremes:
- A pure tone (narrow-band to the extreme).
- A white time series (wide-band to the extreme).
A pure tone
A pure tone has the narrowest band width of all time series (theoretically it
has zero width). As you will
see, a pure tone in the absence of noise is totally predictable.
The results of applying the adaptive prediction process to a pure tone are shown in
Figure 3 and Figure 4.
Figure 3
|
The time series data
Going from top to bottom, Figure 3 shows the time series output for the first, second, and thirteenth
pages. (Recall that each trace on each page
shows 230 samples of the time series data.) The black trace shows the
input to the adaptive filter and the red trace shows the target. The blue
trace shows the output from the adaptive filter. The green trace shows the error.
In the top page of Figure 3, the blue output is small and the green error is large. By
the thirteenth page, the blue output is almost an exact match for the red target
and the green error has been reduced to almost zero.
The numeric prediction error
The numeric prediction error (along with some other information) is shown in
Figure 4.
RMS target: 8.838387896368511 RMS error: 0.0027504970625717136 Percent error: 0.031119895334100794 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 3 predictionDistance: 6 signalScale: 12.5 noiseScale: 0.0 bpFilterLength: 1 rmsAveragingLength: 9890 Figure 4 |
As you can see in Figure 4, the prediction error was only 0.03-percent of the
target.
The prediction distance
Also note in Figure 4 that the prediction distance is six samples.
In other words, the adaptive process was asked to design a convolution filter
that can predict the value of a pure tone six samples into the future.
The adaptive filter length
Also note that the adaptive filter contained only three coefficients in this
scenario.
A somewhat pathological case
This is a case for which it is easy to develop a purely deterministic
mathematical solution. All that is required is the solution of a set of
simultaneous equations that will produce a convolution filter whose amplitude
response is unity and whose phase response is a specified value at the frequency
of the tone.
The case of a pure tone in the total absence of noise rarely occurs in the
real world. Thus, the mathematical solution described above can produce
undesirable results. While the amplitude response can be forced to unity
at a particular frequency by solving the simultaneous equations, it is likely to be much larger than unity at other
frequencies. If there is noise at those other frequencies, that noise will be
amplified in the output of the adaptive filter. A much more realistic
scenario is shown in Figure 5 and
Figure 6.
A more realistic scenario
As you can see, the green error is not zero in Figure 5. In fact, the
error never goes to zero. As shown in Figure 6, the prediction error is approximately ten percent
of the target.
RMS target: 8.866400709397215 RMS error: 0.9514570545236098 Percent error: 10.731040539541494 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 3 predictionDistance: 6 signalScale: 12.5 noiseScale: 1.25 bpFilterLength: 1 rmsAveragingLength: 9890 Figure 6 |
A mathematical solution
While there is a non-adaptive mathematical solution for this scenario, it is
by no means a simple one and it is not deterministic. The solution involves computing a cross-correlation
function between the signal plus noise and the target and then solving a matrix
equation to compute the coefficient values for the prediction filter.
Probably better behaved
The frequency response of the adaptive filter developed for this
scenario is probably better behaved than is the case for the pure tone shown in
Figure 3. It is unlikely that the
amplitude response
for this scenario will amplify the
noise at frequencies other than the frequency of the tone, at least not in
significant ways. (This characteristic could be improved by using a
longer adaptive filter.)
Increased prediction error
On the other hand, the prediction error for
this scenario is significantly greater than the prediction error for the
scenario involving the pure tone in the absence of noise.
(By the way, increasing the filter length for the scenario involving
the pure tone probably won’t improve the final prediction error, but it
may cause
the adaptive process to converge to a solution more quickly.)
Predictability of white noise
Now, let’s take a look at the other extreme: white noise.
The curve in the left panel of Figure 9 shows a convolution filter having two
coefficients.
The top curve in the right panel shows the amplitude
response of that convolution filter computed and displayed from a frequency of
zero to the Nyquist folding frequency. The bottom curve in the right panel
shows the phase response of the convolution filter.
Apply band pass filter to white noise
This convolution filter was applied to the white noise, producing an output
time series having approximately one-half the bandwidth of the white
noise.
(Note that there are several different ways to measure bandwidth.
This estimate is based on the width of the lobe between the three-db down
points in the amplitude response.)
The time series data
The adaptive prediction process was applied to this time series in an attempt
to predict the value of the time series with a prediction distance of one sample
and an adaptive filter length of 26 coefficients. The results are shown in
Figure 10 and Figure 11.
Figure 10
|
The top panel in Figure 10 shows the time series at the beginning of the run.
The bottom panel shows the time series ending at adaptive iteration number 9890. As
you can see in the bottom panel of Figure 9, the blue output trace from the
adaptive filter does look something like the red target trace. The green
error trace in the bottom panel is smaller than the green output trace at the
beginning of the run in the top panel.
The numeric prediction error
As shown in Figure 11, the prediction error was reduced from 100 percent for white noise to
about 82 percent for this scenario. Reducing the bandwidth of
the white noise caused the time series to become partially predictable.
RMS target: 8.889476886391076 RMS error: 7.3078201913053915 Percent error: 82.20753914657176 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 26 predictionDistance: 1 signalScale: 0.0 noiseScale: 12.5 bpFilterLength: 2 rmsAveragingLength: 9890 Figure 11 |
Bandwidth at one-sixteenth of white noise
Apply the band pass filter
The filter shown in Figure 12 was applied to the white noise producing the
time series shown in the top trace in each panel of
Figure 13.
Figure 13
|
The time series output
This time series was subjected to the adaptive prediction process with a
prediction distance of one sample and an adaptive filter length of 26
coefficients, producing the results shown in Figure 13 and
Figure 14.
The top panel in Figure 13 shows the time series at the beginning of the run.
The bottom panel shows the time series at the end of 9890 adaptive iterations. As you can
see, the blue output trace from the prediction filter is a reasonably good
representation of the red target trace in the bottom panel of Figure 13. Also, the
green error trace in the bottom panel is much smaller than the green error trace
at the beginning of the run in the top panel.
The numeric prediction error
In fact, this reduction in bandwidth caused the prediction error to be
reduced to about forty-six percent as shown in Figure 14.
RMS target: 7.490714340573816 RMS error: 3.479276931459588 Percent error: 46.447865627633355 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 26 predictionDistance: 1 signalScale: 0.0 noiseScale: 37.5 bpFilterLength: 16 rmsAveragingLength: 9890 Figure 14 |
Predictability of a very narrow-band time series
Let’s examine one more set of experimental results. These are the
results produced by the default program parameters that are used if the user
doesn’t provide the appropriate number of command-line parameters.
For this experiment, we will reduce the bandwidth to approximately 1/128 of
the bandwidth of the white noise shown in Figure 7.
The band pass filter
Apply the band pass filter
As you can see, the bandwidth of this filter is quite narrow, as compared to
Figure 12, for example. This filter was applied
to white noise, producing the top (black) trace in both panels of
Figure 17.
Figure 17
|
As before, the top panel in Figure 17 shows the time series at the beginning
of the run. The bottom panel shows the time series at the end of 9890
adaptive iterations. The output from the adaptive filter (blue) is
a good replica of the target (red). Thus, the green error trace is
quite small.
The numeric prediction error
Figure 18 shows that this reduction in bandwidth
resulted in a reduction in the prediction error to about 23 percent.
RMS target: 6.482512172567318 RMS error: 1.4847104556284205 Percent error: 22.903319208738477 feedbackGain: 1.0E-5 numberAdaptiveIterations: 9890 adaptiveFilterLength: 26 predictionDistance: 1 signalScale: 0.0 noiseScale: 87.5 bpFilterLength: 128 rmsAveragingLength: 9890 Figure 18 |
This is the longest convolution filter (128 coefficients) that is
supported by the program in its current state. However, this limitation
has nothing to do with the process of applying the filter to the noise.
Rather, it has to do with the method named displayFreqResponse that is
used to compute and display the frequency response of the filter. As an
exercise, you may find it interesting to eliminate that
limitation and observe how the prediction error behaves as you use longer and
longer convolution filters (narrower and narrower bandwidth).
Probably the easiest way to eliminate the limitation of 128 filter
coefficients would be to disable the call to the method named
displayFreqResponse and simply forego the computation and display of the
frequency response. By now, you should have a pretty good idea how the
frequency response behaves as you increase the length of the convolution filter
anyway.
(In addition, you will need to disable the code that limits the filter
length to 128 when you enter a command-line parameter greater than 128.)
That’s it for experimental results. I recommend that you do some
experimentation on your own to gain a better understanding as to how the
predictability behaves with respect to the various program parameters.
Now let’s see some code.
Discussion
and Sample Code
The class named Adapt09
The class named Adapt09 is presented in its entirety in Listing 14 near the
end of the lesson.
I will discuss the program in fragments. The beginning of the class named Adapt09 and the beginning of the
main method are shown in Listing 1.
class Adapt09{ public static void main(String[] args){ //Default parameter values double feedbackGain = 0.00001; int numberAdaptiveIterations = 9890;//fixed value int adaptiveFilterLength = 26; int predictionDistance = 1; double signalScale = 0; double noiseScale = 87.5; int bpFilterLength = 128;//Must be <= 128. int rmsAveragingLength = 9890;//fixed value if(args.length == 6){ //Get and save command-line parameters. feedbackGain = Double.parseDouble(args[0]); adaptiveFilterLength = Integer.parseInt(args[1]); predictionDistance = Integer.parseInt(args[2]); signalScale = Double.parseDouble(args[3]); noiseScale = Double.parseDouble(args[4]); bpFilterLength = Integer.parseInt(args[5]); if(bpFilterLength > 128){ //Make sure this value is not greater than 128. bpFilterLength = 128; System.out.println( "bpFilterLength must be <= 128"); System.out.println("Using bpFilterLength = 128"); }//end if }else{ System.out.println( "Using default program parameters."); }//end else |
All of the code in Listing 1 is straightforward and shouldn’t require
explanation beyond the comments embedded in the code.
Create the band pass filter
The code in Listing 2:
- Creates an array object suitable for storing the band pass filter.
- Creates an object of the Adapt09 class, making it possible to call a
method of that class to get the convolution filter coefficients for the
band pass filter. - Iteratively invokes the method named getCosine to get the filter
coefficients and store them in the array mentioned above.
//Create an array object to store the bandpass filter // coefficients. double[] bpFilterOperator = new double[bpFilterLength]; //Instantiate a new object of the Adapt09 class. Adapt09 thisObject = new Adapt09(); //Prepare the bandpass filter,which is a truncated // sinusoid with a frequency of one-eighth of the // sampling frequency. for(int cnt = 0;cnt < bpFilterLength;cnt++){ bpFilterOperator[cnt] = thisObject.getCosine(cnt); }//end for loop |
The getCosine method
For every scenario, the band pass filter is simply a truncated cosine function
with a frequency that is one-eighth of the sampling frequency. Thus, the
coefficient values repeat with eight coefficient values occurring during each
cycle of the cosine function.
The source code for the getCosine method is shown in Listing 14 near
the end of the lesson. That code is straightforward and shouldn’t require
further explanation.
The remainder of the main method
The remainder of the main method is shown in Listing 3.
//Invoke the method named process. thisObject.process(feedbackGain, numberAdaptiveIterations, rmsAveragingLength, adaptiveFilterLength, signalScale, noiseScale, bpFilterOperator, predictionDistance); //Display program parameters at the end of the run. System.out.println("nfeedbackGain: " + feedbackGain); System.out.println("numberAdaptiveIterations: " + numberAdaptiveIterations); System.out.println("adaptiveFilterLength: " + adaptiveFilterLength); System.out.println("predictionDistance: " + predictionDistance); System.out.println("signalScale: " + signalScale); System.out.println("noiseScale: " + noiseScale); System.out.println("bpFilterLength: " + bpFilterLength); System.out.println("rmsAveragingLength: " + rmsAveragingLength); }//end main |
The code in Listing 3 invokes the process method of the Adapt09
class, where all the hard work is done. When the process method
returns, the code in Listing 3 displays some summary information (see
Figure 18 for example) and terminates the program.
The process method
Listing 4 shows the beginning of the process method.
void process(double feedbackGain, int numberAdaptiveIterations, int rmsAveragingLength, int adaptiveFilterLength, double signalScale, double noiseScale, double[] bpFilterOperator, int predictionDistance){ //Display the bandpassFilterOperator //First instantiate a plotting object. PlotALot01 bpFilterPlotObj = new PlotALot01( "Noise Filter Operator", (bpFilterOperator.length * 2) + 8,148,70,2,0,0); //Feed the data to the plotting object. for(int cnt = 0;cnt < bpFilterOperator.length;cnt++){ bpFilterPlotObj.feedData(40*bpFilterOperator[cnt]); }//end for loop //Cause the graph to be displayed on the computer // screen in the upper left corner. bpFilterPlotObj.plotData(0,0); //Now compute and plot the frequency response of the // bandpass filter. //Instantiate a plotting object for two channels of // frequency response data. One channel is for // the amplitude and the other channel is for the // phase. PlotALot03 bpFilterFreqPlotObj = new PlotALot03( "Noise Filter Freq Response",264,148,35,2,0,0); //Compute the frequency response and feed the results // to the plotting object. displayFreqResponse( bpFilterOperator,bpFilterFreqPlotObj,128,0); //Cause the frequency response data stored in the // plotting object to be displayed on the screen in // the middle position. bpFilterFreqPlotObj.plotData(0,148); |
All of the code in Listing 4 is the same as, or very similar to code that has
been previously explained in lessons referred to in the
References section of this lesson. Therefore, it shouldn’t be
necessary to provide further explanation of that code beyond the comments
contained in Listing 4.
Two delay lines
Listing 5 instantiates an array object that will be used as a delay line for
the signal plus white noise data in order to convolve the samples with the
band pass filter later.
double[] whiteNoiseDelayLine = new double[bpFilterOperator.length]; |
Note that the length of the delay line is determined by the length of the
array created earlier and referred to by the reference variable named
bpFilterOperator. In other words, the length of the delay line matches
the length of the convolution operator used to implement the band pass filter.
The length of the convolution filter is a user input parameter.
Listing 6 instantiates an array object that will be used as a delay line for
the filtered signal plus noise data. This delay line is used to obtain a
target sample that is advanced in time relative to the data that is used to predict the
target. The target sample is advanced in time by a number of samples equal
to predictionDistance.
double[] signalPlusNoiseDelayLine = new double[predictionDistance + 1]; |
The delay line created in Listing 6 corresponds to the box labeled Delay
in the
block diagram.
A plotting object and working variables
Listing 7 instantiates a plotting object that will be used to plot the four
channels of time-series data shown in Figure 3.
This code is similar to code that was explained in previous lessons referred to
in the References section of this lesson.
//Instantiate a plotting object for four channels of // time-series data at 230 samples per page. int sampPerPage = 230; PlotALot05 timePlotObj = new PlotALot05( "Time-Domain Adaptive Data", (2*sampPerPage + 8),148,25,2,0,0); //Declare and initialize working variables. double output = 0; double err = 0; double target = 0; double input = 0; double signal = 0; double whiteNoise = 0; boolean adaptOn; double targetSumSq = 0; double errSumSq = 0; |
Listing 7 also declares and initializes some working variables.
None of the code in Listing 7 should require further explanation.
An adaptive engine object
Listing 8 instantiates an object of the class named AdaptEngine02 to
handle the adaptive behavior of this program. This class was explained
earlier in the lesson entitled
Adaptive Identification and Inverse
Filtering using Java. That explanation won’t be repeated
here.
AdaptEngine02 adapter = new AdaptEngine02( adaptiveFilterLength,feedbackGain); |
The object of the AdaptEngine02 class represents the shaded portion of
the
block diagram.
Perform adaptive iterations
Listing 9 contain the beginning of a for loop that is used to perform
the required number of iterations. The required number of iterations is
the sum of the specified number of adaptive iterations plus the non-adaptive
iterations that are used to compute the percent error after the adaptive update
process is disabled.
for(int cnt = 0; cnt < numberAdaptiveIterations + rmsAveragingLength; cnt++){ |
Routine processing steps
Listing 10 performs a number of routine processing steps. These steps
are either completely straightforward, or are very similar to code that has been
explained in previous lessons, which are referred to in the
References section of this lesson. Therefore, no
explanation beyond the embedded comments will be provided in this
lesson.
//The following variable is used to control whether // or not the adapt method of the adaptive engine // updates the filter coefficients when it is called. // The filters are updated when this variable is // true and are not updated when this variable is // false. adaptOn = true; //Get and scale the next sample of white noise data // from a random number generator. Before scaling by // noiseScale, the values are uniformly distributed // from -1.0 to 1.0. whiteNoise = noiseScale*(2*(random() - 0.5)); //Get and scale the next sample of signal. The // signal is a pure tone with a frequency equal to // one-eighth of the sampling frequency. Before // scaling by signalScale, the peak-to-peak value // is 2. signal = signalScale * getCosine(cnt); //Insert the signal plus white noise data into the // delay line that will be used to convolve the // signal plus white noise data with the bandpass // filter. double signalPlusNoise = whiteNoise + signal; flowLine(whiteNoiseDelayLine,signalPlusNoise); //Declare a variable to receive the filtered signal // plus noise sample. double filteredSignalPlusNoise = 0; //Apply the bandpass filter operator to the signal // plus white noise data. If the filter length is 1, // bypass the filtering operation and use the raw // signal plus white noise. if(bpFilterOperator.length == 1){ filteredSignalPlusNoise = signalPlusNoise; }else{ filteredSignalPlusNoise = reverseDotProduct( whiteNoiseDelayLine, bpFilterOperator)/(bpFilterOperator.length/2.0); }//end else //Insert the filtered signal plus noise into the // delay line that is used to obtain a time-advanced // sample of the target. flowLine( signalPlusNoiseDelayLine,filteredSignalPlusNoise); //Declare a variable that will be populated with the // results returned by the adapt method of the // adaptive engine. AdaptiveResult result = null; //Disable adaptive updates when the specified number // of adaptive iterations has been performed. if(cnt > numberAdaptiveIterations){ adaptOn = false; }//end if |
Perform the adaptive processing
The most important code in this class is contained in Listing 11. This
code establishes the appropriate input values and invokes the adapt
method of the AdaptEngine02 object to perform the adaptive updates to the adaptive filter
coefficients.
input = signalPlusNoiseDelayLine[0];//historical samp target = signalPlusNoiseDelayLine[predictionDistance]; result = adapter.adapt(input,target,adaptOn); |
Just to get our bearings, input in Listing 11 corresponds to
x(k) in the
block diagram. Similarly, target in Listing 11 corresponds to
d(k) in the
block diagram.
The oldest data in the delay line referred to by signalPlusNoiseDelayLine
in Listing 11 is the data value at index 0.
The newest data in the delay line is the data value at an index value of
predictionDistance. (The length of the delay line is
predictionDistance + 1.)
Thus, the oldest value in the delay line is fed into the adaptive filter as
x(k) in the
block diagram. The newest value in the delay line is used to compute
the prediction error as shown by d(k) in the
block diagram. As a result, the LMS algorithm attempts to develop an
adaptive convolution filter that will act on the incoming time series to predict
the value of a sample that is predictionDistance samples in the future.
Complete the for loop
Listing 12 completes the for loop that controls the required number of
iterations.
//Get and save adaptive results. output = result.output; err = result.err; //Compute values that will be used later to compute // the percent error. This computation is performed // only after adaptive updates have been disabled. if(!adaptOn){ //Accumulate the sum of the squares of the target // and error values. targetSumSq += target * target; errSumSq += err * err; }//end if //Feed the time series data to the plotting object. // Plot only two pages of data after adaptive updates // are disabled. if(cnt <= numberAdaptiveIterations + 2*sampPerPage){ timePlotObj.feedData(input,target,output,err); }//end if }//End for loop on required number of iterations. |
The code in Listing 12 is straightforward and shouldn’t require an
explanation beyond the embedded comments.
Complete the adapt method
Listing 13 shows the remaining code in the adapt method of the
AdaptEngine02 object.
//Compute and display the mean square values double rmsTarget = sqrt(targetSumSq/rmsAveragingLength); double rmsError = sqrt(errSumSq/rmsAveragingLength); System.out.println("nRMS target: " + rmsTarget); System.out.println("RMS error: " + rmsError); System.out.println( "Percent error: " + 100.0*(rmsError)/(rmsTarget)); //Cause the data stored in the plotting object to be // plotted. timePlotObj.plotData(0,296); }//end process method |
This code is straightforward and shouldn’t require further explanation.
Run the Program
I encourage you to copy the code for the class named Adapt09 from the section entitled
Complete Program Listings.
Compile and execute the program. Experiment with the code. Make changes to the code, recompile, execute,
and observe the results of your changes.
Modify the following program parameters and observe the results of your
changes. See if you can explain the results of your changes.
- feedbackGain
- adaptiveFilterLength
- predictionDistance
- signalScale
- noiseScale
- bandpassFilterLength
For example, what happens if you use a large value for feedbackGain?
What happens if you use a very small value for feedbackGain? What
happens if you use a negative value for feedbackGain? Can you
explain the results that you experience?
What happens if you use a large value for predictionDistance?
In particular I encourage you to make the changes suggested in the section
entitled An exercise for the student
and evaluate the results of those changes.
Other classes required
In addition to the class named Adapt09, you will need access to the
following classes. The source code for these classes can be found in the
lessons indicated.
- AdaptEngine02:
Adaptive Identification and Inverse
Filtering using Java - AdaptiveResult:
Adaptive Identification and Inverse
Filtering using Java - ForwardRealToComplex01:
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm - PlotALot01:
Plotting
Large Quantities of Data using Java - PlotALot03:
Plotting
Large Quantities of Data using Java - PlotALot05:
Adaptive
Filtering in Java, Getting Started
Summary
In this lesson, I showed you how to use a Java adaptive filter to predict
future values in a time series. I also introduced you to the
relationship between the properties of the time series and the quality of the
prediction.
References
In preparation for understanding the material in this lesson, I recommend
that you study the material in the following previously-published lessons:
- 100 Periodic
Motion and Sinusoids - 104 Sampled
Time Series - 108
Averaging Time Series - 1478
Fun with Java, How and Why Spectral Analysis Works - 1482
Spectrum Analysis using Java, Sampling Frequency, Folding Frequency, and the
FFT Algorithm - 1483
Spectrum Analysis using Java, Frequency Resolution versus Data Length - 1484
Spectrum Analysis using Java, Complex Spectrum and Phase Angle - 1485
Spectrum Analysis using Java, Forward and Inverse Transforms, Filtering in
the Frequency Domain - 1487
Convolution and Frequency Filtering in Java - 1488
Convolution and Matched Filtering in Java - 1492
Plotting Large Quantities of Data using Java - 2350
Adaptive Filtering in Java, Getting Started - 2352
An Adaptive Whitening Filter in Java - 2354
A General-Purpose LMS Adaptive Engine in Java - 2356 An Adaptive Line Tracker in Java
- 2358 Adaptive Identification and Inverse Filtering using Java
- 2360 Adaptive Noise Cancellation using Java
Complete Program Listings
A complete listings of the class discussed in this lesson is shown in Listing
14 below.
/*File Adapt09.java Copyright 2005, R.G.Baldwin The purpose of this program is to illustrate an adaptive prediction system. The user can experiment with adaptive prediction by making changes to the following parameters and observing printed and graphic results: feedbackGain adaptiveFilterLength predictionDistance signalScale noiseScale bandpassFilterLength (and hence bandwidth) See the following URL for a brief description and a block diagram of an adaptive prediction system. http://www.mathworks.com/access/helpdesk/help/toolbox/ filterdesign/adaptiv8.html#5576 This program requires the following classes: Adapt09.class AdaptEngine02.class AdaptiveResult.class ForwardRealToComplex01.class PlotALot01.class PlotALot03.class PlotALot05.class This program uses the adaptive engine named AdaptEngine02 to adaptively develop a convolution prediction filter. One of the inputs to the adaptive engine is the current sample of the sum of signal plus noise. This is the target of the prediction process. The other input to the adaptive engine is an historical sample of signal plus noise. The adaptive engine develops a convolution filter that attempts to use historical signal plus noise samples to predict the value of the current sample of signal plus noise (target). Thus, the program attempts to adaptively develop a convolution filter that can operate on a time series to predict a future value of the time series. The distance in time to the future value is a user input parameter. The program performs 9890 iterations during which the convolution filter coefficients are adaptively updated. Then the adaptive update process is disabled. The program then performs 9890 more iterations during which the quality of the prediction is measured and reported. The quality of the prediction is measured by: 1. Computing the RMS value of the target averaged over 9890 samples. 2. Computing the RMS value of the prediction error averaged over 9890 samples. 3. Computing the percentage of the RMS target value represented by the RMS error. The signal consists of a pure tone at a frequency that is one eighth of the sampling frequency (eight samples per cycle). The peak-to-peak value of the signal prior to scaling is 2. The noise consists of white noise produced by a random number generator with a uniform distribution between -1.0 and 1.0 prior to scaling. The scaled signal is added to the scaled white noise. The sum of signal plus white noise can be passed through a bandpass filter with a center frequency equal to one- eighth of the sampling frequency. The center frequency of the bandpass filter is the same as the frequency of the signal. Thus, the signal plus noise consists of a pure tone surrounded by noise for which the noise bandwidth can be controlled by the user. The frequency band for the noise is centered on the signal frequency. The user controls the signal level and the noise level, and hence the signal-to-noise ratio. The bandpass filter consists of a convolution filter in the form of a truncated sinusoid with a frequency of one-eighth of the sampling frequency. The user specifies the length of the convolution filter, (up to a limit of 128 coefficients), thereby specifying the bandwidth which is roughly proportional to the reciprocal of the length of the convolution filter. The shape of the amplitude response of the pass band for the filter is roughly that of a sin(x)/x function. The program produces three graphs in a vertical stack on the screen. The graphs display the following information in order from top to bottom: 1. The bandpass convolution filter used to filter the raw signal plus noise. 2. The frequency response of the bandpass filter used to filter the raw signal plus noise. 3. Four traces of adaptive time-series data. Graph 3 consists of multiple pages stacked on top of one another. You must move the pages on the top of the stack to view the pages further down. The pages on the top of the stack represent the results produced early in the adaptive process while those further down represent the results produced later in the adaptive process. The four time series that are plotted are, from top to bottom in the colors indicated: 1. (Black) The historical signal plus noise input to the adaptive prediction filter. 2. (Red) The signal plus noise target for the adaptive prediction filter. 3. (Blue) The output from the adaptive prediction filter. 4. (Green) The error, which is the difference between the red target and the blue output from the adaptive prediction filter. Ideally, this trace approaches zero as the process converges to a solution. User input is provided by way of command-line parameters. If no command-line parameters are provided, default values are used for the program parameters. The command-line parameters in order are: 1. double feedbackGain: The gain used in the adaptive feedback loop. The default value is 0.00001. 2. int adaptiveFilterLength: The default value is 26. 3. int predictionDistance: The distance in the future, (measured in samples), that the adaptive process attempts to predict the value of signal plus noise (target). The default value is 1 sample. 4. double signalScale: The default value is 0. Adjust this and the following parameter to adjust the signal to noise ratio, and also to cause the time-series plots to be in good plotting range. 5. double noiseScale: The default value is 87.5. 6. int bandpassFilterLength: The length of the convolution filter that is applied to the raw signal plus noise. This value must be less than or equal to 128. If it is greater than 128, it is automatically set to 128. Tested using J2SE 5.0 and WinXP. J2SE 5.0 or later is required. **********************************************************/ import static java.lang.Math.*;//J2SE 5.0 req class Adapt09{ public static void main(String[] args){ //Default parameter values double feedbackGain = 0.00001; int numberAdaptiveIterations = 9890;//fixed value int adaptiveFilterLength = 26; int predictionDistance = 1; double signalScale = 0; double noiseScale = 87.5; int bpFilterLength = 128;//Must be <= 128. int rmsAveragingLength = 9890;//fixed value if(args.length == 6){ //Get and save command-line parameters. feedbackGain = Double.parseDouble(args[0]); adaptiveFilterLength = Integer.parseInt(args[1]); predictionDistance = Integer.parseInt(args[2]); signalScale = Double.parseDouble(args[3]); noiseScale = Double.parseDouble(args[4]); bpFilterLength = Integer.parseInt(args[5]); if(bpFilterLength > 128){ //Make sure this value is not greater than 128. bpFilterLength = 128; System.out.println( "bpFilterLength must be <= 128"); System.out.println("Using bpFilterLength = 128"); }//end if }else{ System.out.println( "Using default program parameters."); }//end else //Create an array object to store the bandpass filter // coefficients. double[] bpFilterOperator = new double[bpFilterLength]; //Instantiate a new object of the Adapt09 class. Adapt09 thisObject = new Adapt09(); //Prepare the bandpass filter,which is a truncated // sinusoid with a frequency of one-eighth of the // sampling frequency. for(int cnt = 0;cnt < bpFilterLength;cnt++){ bpFilterOperator[cnt] = thisObject.getCosine(cnt); }//end for loop //Invoke the method named process. thisObject.process(feedbackGain, numberAdaptiveIterations, rmsAveragingLength, adaptiveFilterLength, signalScale, noiseScale, bpFilterOperator, predictionDistance); //Display program parameters at the end of the run. System.out.println("nfeedbackGain: " + feedbackGain); System.out.println("numberAdaptiveIterations: " + numberAdaptiveIterations); System.out.println("adaptiveFilterLength: " + adaptiveFilterLength); System.out.println("predictionDistance: " + predictionDistance); System.out.println("signalScale: " + signalScale); System.out.println("noiseScale: " + noiseScale); System.out.println("bpFilterLength: " + bpFilterLength); System.out.println("rmsAveragingLength: " + rmsAveragingLength); }//end main //-----------------------------------------------------// //This is the primary adaptive processing and plotting // method for the program. void process(double feedbackGain, int numberAdaptiveIterations, int rmsAveragingLength, int adaptiveFilterLength, double signalScale, double noiseScale, double[] bpFilterOperator, int predictionDistance){ //Display the bandpassFilterOperator //First instantiate a plotting object. PlotALot01 bpFilterPlotObj = new PlotALot01( "Noise Filter Operator", (bpFilterOperator.length * 2) + 8,148,70,2,0,0); //Feed the data to the plotting object. for(int cnt = 0;cnt < bpFilterOperator.length;cnt++){ bpFilterPlotObj.feedData(40*bpFilterOperator[cnt]); }//end for loop //Cause the graph to be displayed on the computer // screen in the upper left corner. bpFilterPlotObj.plotData(0,0); //Now compute and plot the frequency response of the // bandpass filter. //Instantiate a plotting object for two channels of // frequency response data. One channel is for // the amplitude and the other channel is for the // phase. PlotALot03 bpFilterFreqPlotObj = new PlotALot03( "Noise Filter Freq Response",264,148,35,2,0,0); //Compute the frequency response and feed the results // to the plotting object. displayFreqResponse( bpFilterOperator,bpFilterFreqPlotObj,128,0); //Cause the frequency response data stored in the // plotting object to be displayed on the screen in // the middle position. bpFilterFreqPlotObj.plotData(0,148); //Instantiate an array object that will be used as a // delay line for the signal plus white noise data in // order to convolve the samples with the bandpass // filter. double[] whiteNoiseDelayLine = new double[bpFilterOperator.length]; //Instantiate an array object that will be used as a // delay line for the filtered signal plus noise data. // This delay line is used to obtain a target sample // advanced in time relative to the data that is used // to predict the target. The target sample is // advanced in time by a number of samples equal to // predictionDistance. double[] signalPlusNoiseDelayLine = new double[predictionDistance + 1]; //Instantiate a plotting object for four channels of // time-series data at 230 samples per page. int sampPerPage = 230; PlotALot05 timePlotObj = new PlotALot05( "Time-Domain Adaptive Data", (2*sampPerPage + 8),148,25,2,0,0); //Declare and initialize working variables. double output = 0; double err = 0; double target = 0; double input = 0; double signal = 0; double whiteNoise = 0; boolean adaptOn; double targetSumSq = 0; double errSumSq = 0; //Instantiate an object to handle the adaptive behavior // of the program. AdaptEngine02 adapter = new AdaptEngine02( adaptiveFilterLength,feedbackGain); //Perform the specified number of iterations. This is // the sum of the specified number of adaptive // iterations plus the non-adaptive iterations that are // used to compute the percent error after the adaptive // update process is disabled. for(int cnt = 0; cnt < numberAdaptiveIterations + rmsAveragingLength; cnt++){ //The following variable is used to control whether // or not the adapt method of the adaptive engine // updates the filter coefficients when it is called. // The filters are updated when this variable is // true and are not updated when this variable is // false. adaptOn = true; //Get and scale the next sample of white noise data // from a random number generator. Before scaling by // noiseScale, the values are uniformly distributed // from -1.0 to 1.0. whiteNoise = noiseScale*(2*(random() - 0.5)); //Get and scale the next sample of signal. The // signal is a pure tone with a frequency equal to // one-eighth of the sampling frequency. Before // scaling by signalScale, the peak-to-peak value // is 2. signal = signalScale * getCosine(cnt); //Insert the signal plus white noise data into the // delay line that will be used to convolve the // signal plus white noise data with the bandpass // filter. double signalPlusNoise = whiteNoise + signal; flowLine(whiteNoiseDelayLine,signalPlusNoise); //Declare a variable to receive the filtered signal // plus noise sample. double filteredSignalPlusNoise = 0; //Apply the bandpass filter operator to the signal // plus white noise data. If the filter length is 1, // bypass the filtering operation and use the raw // signal plus white noise. if(bpFilterOperator.length == 1){ filteredSignalPlusNoise = signalPlusNoise; }else{ filteredSignalPlusNoise = reverseDotProduct( whiteNoiseDelayLine, bpFilterOperator)/(bpFilterOperator.length/2.0); }//end else //Insert the filtered signal plus noise into the // delay line that is used to obtain a time-advanced // sample of the target. flowLine( signalPlusNoiseDelayLine,filteredSignalPlusNoise); //Declare a variable that will be populated with the // results returned by the adapt method of the // adaptive engine. AdaptiveResult result = null; //Disable adaptive updates when the specified number // of adaptive iterations has been performed. if(cnt > numberAdaptiveIterations){ adaptOn = false; }//end if //Establish the appropriate input values and perform // the adaptive updates to the adaptive filter // coefficients. input = signalPlusNoiseDelayLine[0];//historical samp target = signalPlusNoiseDelayLine[predictionDistance]; result = adapter.adapt(input,target,adaptOn); //Get and save adaptive results. output = result.output; err = result.err; //Compute values that will be used later to compute // the percent error. This computation is performed // only after adaptive updates have been disabled. if(!adaptOn){ //Accumulate the sum of the squares of the target // and error values. targetSumSq += target * target; errSumSq += err * err; }//end if //Feed the time series data to the plotting object. // Plot only two pages of data after adaptive updates // are disabled. if(cnt <= numberAdaptiveIterations + 2*sampPerPage){ timePlotObj.feedData(input,target,output,err); }//end if }//End for loop on required number of iterations. //Compute and display the mean square values double rmsTarget = sqrt(targetSumSq/rmsAveragingLength); double rmsError = sqrt(errSumSq/rmsAveragingLength); System.out.println("nRMS target: " + rmsTarget); System.out.println("RMS error: " + rmsError); System.out.println( "Percent error: " + 100.0*(rmsError)/(rmsTarget)); //Cause the data stored in the plotting object to be // plotted. timePlotObj.plotData(0,296); }//end process method //-----------------------------------------------------// //This method simulates a tapped delay line. It receives // a reference to an array and a value. It discards the // value at index 0 of the array, moves all the other // values by one element toward 0, and inserts the new // value at the top of the array. void flowLine(double[] line,double val){ for(int cnt = 0;cnt < (line.length - 1);cnt++){ line[cnt] = line[cnt+1]; }//end for loop line[line.length - 1] = val; }//end flowLine //-----------------------------------------------------// void displayFreqResponse( double[] filter,PlotALot03 plot,int len,int zeroTime){ //Create the arrays required by the Fourier Transform. double[] timeDataIn = new double[len]; double[] realSpect = new double[len]; double[] imagSpect = new double[len]; double[] angle = new double[len]; double[] magnitude = new double[len]; //Copy the filter into the timeDataIn array System.arraycopy(filter,0,timeDataIn,0,filter.length); //Compute DFT of the filter from zero to the folding // frequency and save it in the output arrays. ForwardRealToComplex01.transform(timeDataIn, realSpect, imagSpect, angle, magnitude, zeroTime, 0.0, 0.5); //Find the absolute peak value. Begin with a negative // peak value with a large magnitude and replace it // with the largest magnitude value. double peak = -9999999999.0; for(int cnt = 0;cnt < magnitude.length;cnt++){ if(peak < abs(magnitude[cnt])){ peak = abs(magnitude[cnt]); }//end if }//end for loop //Normalize to 20 times the peak value for(int cnt = 0;cnt < magnitude.length;cnt++){ magnitude[cnt] = 20*magnitude[cnt]/peak; }//end for loop //Now feed the normalized data to the plotting // object. for(int cnt = 0;cnt < magnitude.length;cnt++){ plot.feedData(magnitude[cnt],angle[cnt]/20); }//end for loop }//end displayFreqResponse //-----------------------------------------------------// //This method receives two arrays and treats each array // as a vector. The two arrays must have the same length. // The program reverses the order of one of the vectors // and returns the vector dot product of the two vectors. double reverseDotProduct(double[] v1,double[] v2){ if(v1.length != v2.length){ System.out.println("reverseDotProduct"); System.out.println("Vectors must be same length."); System.out.println("Terminating program"); System.exit(0); }//end if double result = 0; for(int cnt = 0;cnt < v1.length;cnt++){ result += v1[cnt] * v2[v1.length - cnt - 1]; }//end for loop return result; }//end reverseDotProduct //-----------------------------------------------------// //This method returns the values of a cosine function // sampled eight samples per cycle. double getCosine(int index){ int cnt = index%8; if(cnt == 0){ return 1.0; }else if(cnt == 1){ return 0.7071067811865476; }else if(cnt == 2){ return 0; }else if(cnt == 3){ return -0.7071067811865476; }else if(cnt == 4){ return -1.0; }else if(cnt == 5){ return -0.7071067811865476; }else if(cnt == 6){ return 0; }else if(cnt == 7){ return 0.7071067811865476; }//end else return 0;//Make the compiler happy. }//end getCosine //-----------------------------------------------------// }//end class Adapt09 |
Copyright 2006, Richard G. Baldwin. Reproduction in whole or in part in any
form or medium without express written permission from Richard Baldwin is
prohibited.
About the author
Richard Baldwin is a
college professor (at Austin Community College in Austin, TX) and private
consultant whose primary focus is a combination of Java, C#, and XML. In
addition to the many platform and/or language independent benefits of Java and
C# applications, he believes that a combination of Java, C#, and XML will become
the primary driving force in the delivery of structured information on the Web.
Richard has participated in numerous consulting projects and he
frequently provides onsite training at the high-tech companies located in and
around Austin, Texas. He is the author of Baldwin’s Programming
Tutorials, which have gained a
worldwide following among experienced and aspiring programmers. He has also
published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of
practical experience in Digital Signal Processing (DSP). His first job after he
earned his Bachelor’s degree was doing DSP in the Seismic Research Department of
Texas Instruments. (TI is still a world leader in DSP.) In the following
years, he applied his programming and DSP expertise to other interesting areas
including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has
many years of experience in the application of computer technology to real-world
problems.