September 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Adaptive Identification and Inverse Filtering using Java

  • February 7, 2006
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Discussion and Sample Code

The class named Adapt07

Listing 1 contains the beginning of the class named Adapt07 and the entire main method.

class Adapt07{
  public static void main(String[] args){
    //Default parameter values
    double feedbackGain = 0.0001;
    int numberIterations = 2001;
    int filterLength = 26;//Must be >= 26 for plotting.
    //Six test cases, numbered 1 through 6 are defined
    // later.
    int testCase = 1;
    //A value of true for the following variable causes the
    // adaptive process to attempt to develop an
    // identification filter. A value of false causes the
    // adaptive process to attempt to develop an inverse
    // filter for the path impulse response.
    boolean identification = true;
    
    //The following scale factor is applied to the
    // wideband test data.  This is not an input
    // parameter.
    double wbTestDataScale = 10;
    
    //Process command-line arguments.  Note that because of
    // plotting alignment issues discussed in earlier
    // lessons, the filter length must be at least 26.
    if(args.length != 5){
      System.out.println(
               "Usage with all parameters following the " +
               "program name:n" +
               "java Adapt07n" +
               "feedbackGainn" + 
               "numberIterationsn" + 
               "filterLength >= 26n" +
               "testCase, 1 to 9n" +
               "identification, T or Fn");
               
      System.out.println(
          "Input values were not provided.n"+
          "Using following default values:n" +
          "feedbackGain: " + feedbackGain +
          "nnumberIterations: " + numberIterations +
          "nfilterLength: " + filterLength +
          "ntestCase: " + testCase +
          "nidentification: " + identification);
    }else{//Command line params were provided.
      feedbackGain = Double.parseDouble(args[0]);
      numberIterations = Integer.parseInt(args[1]);
      filterLength = Integer.parseInt(args[2]);
      //FilterLength must be 26 or greater to avoid
      // plotting alignment problems.
      if(filterLength < 26){
        System.out.println(
                   "nfilterLength must be 26 or greater");
        System.out.println("Termnating program");
        System.exit(0);
      }//end if
      testCase = Integer.parseInt(args[3]);
      if(args[4].toUpperCase().equals("T")){
        identification = true;
      }else{
        identification = false;
      }//end else
    
      System.out.println(
          "Input values were provided.n"+
          "Using following values:n" +
          "feedbackGain: " + feedbackGain +
          "nnumberIterations: " + numberIterations +
          "nfilterLength: " + filterLength +
          "ntestCase: " + testCase +
          "nidentification: " + identification);
    }//end else

    //Instantiate a new object of the Adapt07 class
    // and invoke the method named process on that object.
    new Adapt07().process(feedbackGain,
                          numberIterations,
                          filterLength,
                          wbTestDataScale,
                          testCase,
                          identification);
  }//end main

Listing 1

The code in Listing 1 is completely straightforward and shouldn't require any explanation beyond the comments contained in the code.

Note that the main method instantiates an object of the Adapt07 class and invokes the instance method named process on that object.

The process method

Listing 2 shows the beginning of the method named process.

  void process(double feedbackGain,
               int numberIterations,
               int filterLength,
               double wbTestDataScale,
               int testCase,
               boolean identification){

    //The following array will be populated with the
    // adaptive filter for display purposes.
    double[] filter = null;

Listing 2

The code in Listing 2 is also completely straightforward.

Six built in path scenarios

Things begin to get interesting in Listing 3.  The code in Listing 3 instantiates and initializes six array objects to contain the coefficient values for the six path scenarios described earlier.  Each array object contains the coefficient values for the impulse response of a particular path scenario.

    //Define several test cases for the path impulse
    // response.

    //Low-pass filter with short time constant.
    double[] pathA = {1.0,
                      0.5,
                      0.25,
                      0.125,
                      0.0625,
                      0.03125,
                      0.015625,
                      0.0};

    //Low-pass filter with long time constant.
    double[] pathB = {1.0,
                      0.8,
                      0.64,
                      0.512,
                      0.4096,
                      0.32768,
                      0.262144,
                      0.2097152,
                      0.1677721,
                      0.1342176,
                      0.1073740,
                      0.0858992,
                      0.0687193,
                      0.0549754,
                      0.0439803,
                      0.0351842,
                      0.0};

    //High-pass filter with long time constant.
    double[] pathC = {1.0,
                     -0.8,
                      0.64,
                     -0.512,
                      0.4096,
                     -0.32768,
                      0.262144,
                     -0.2097152,
                      0.1677721,
                     -0.1342176,
                      0.1073740,
                     -0.0858992,
                      0.0687193,
                     -0.0549754,
                      0.0439803,
                     -0.0351842,
                      0.0};
                        
    //Mid-pass filter with long time constant.
    double[] pathD = {1.0,
                      0.0,
                     -0.64,
                      0.0,
                      0.4096,
                      0.0,
                     -0.262144,
                      0.0,
                      0.1677721,
                      0.0,
                     -0.1073740,
                      0.0,
                      0.0687193,
                      0.0,
                     -0.0439803,
                      0.0};
                      
    //Simulation of an acoustic signal with echoes.
    double[] pathE = {1.0,
                      0.0,
                      0.64,
                      0.0,
                      0.0,
                      0.32768,
                      0.0,
                      0.0,
                      0.0,
                      0.1342176,
                      0.0,
                      0.0,
                      0.0,
                      0.0,
                      0.0439803,
                      0.0};
    
    //Digital boxcar filter with peak at half the folding
    // frequency.
    double[] pathF = {1.0,
                      0.0,
                     -1.0,
                      0.0,
                      1.0,
                      0.0,
                     -1.0};

Listing 3

Selection of a path scenario

The code in Listing 4 uses the command-line parameter named testCase to select one of the six path scenarios for the run.  This code is straightforward.

    //A reference to the selected path operator will be
    // stored here.
    double[] pathOperator = null;
    
    if(testCase == 1){
      pathOperator = pathA;
    }else if(testCase == 2){
      pathOperator = pathB;
    }else if(testCase == 3){
      pathOperator = pathC;
    }else if(testCase == 4){
      pathOperator = pathD;
    }else if(testCase == 5){
      pathOperator = pathE;
    }else if(testCase == 6){
      pathOperator = pathF;
    }else{
      System.out.println("Invalid testCase");
      System.out.println("Terminating program");
      System.exit(0);
    }//end else

Listing 4

You are encouraged to experiment

Three of the path scenarios defined in Listing 3 and selected in Listing 4 were described or partially described in the earlier section entitled Experimental Results.  You are encouraged to run the program for all six of the scenarios (by specifying the appropriate command-line parameters) and to examine the results.  See if you can explain the results for each of the six scenarios.

You are also encouraged to modify the coefficient values in one or more of the array objects in Listing 3 to create your own path scenarios.  Then recompile and run the program for your scenarios.  See if you can explain the results.

See if you can create path scenarios for which either the identification filter or the inverse filter fails to converge to a solution.  If so, see if you can explain why the LMS adaptive algorithm won't converge for that path scenario.

Display the pathOperator and its frequency response

The code in Listing 5 uses capabilities previously described in earlier lessons referred to in the References section to display the impulse response and the frequency response of the path as shown in Figure 4 and Figure 12.

    //Display the pathOperator
    //First instantiate a plotting object.
    PlotALot01 pathOperatorObj = new PlotALot01("Path",
               (pathOperator.length * 4) + 8,148,70,4,0,0);

    //Feed the data to the plotting object.
    for(int cnt = 0;cnt < pathOperator.length;cnt++){
      pathOperatorObj.feedData(40*pathOperator[cnt]);
    }//end for loop
    
    //Cause the graph to be displayed on the computer
    // screen in the upper left corner.
    pathOperatorObj.plotData(0,0);
    
    //Now compute and plot the frequency response of the
    // selected path
    
    //Instantiate a plotting object for two channels of
    // frequency response data.  One channel is for
    // the amplitude and the other channel is the phase.
    PlotALot03 pathFreqPlotObj = 
                   new PlotALot03("Path",264,148,35,2,0,0);
                   
    //Compute the frequency response and feed the results
    // to the plotting object.
    displayFreqResponse(pathOperator,pathFreqPlotObj,
                                                    128,0);
                       
    //Cause the frequency response data stored in the
    // plotting object to be displayed on the screen in
    // the top row of images.
    pathFreqPlotObj.plotData(112,0);

Listing 5

If you have been studying the code in the earlier lessons in this series (see References) the code in Listing 5 should not require further explanation.

Instantiate an adaptive engine object

Listing 6 instantiates an object of the AdaptEngine02 class to handle the adaptive behavior of the program.  The use of the class named AdaptEngine02 is new to this lesson.  You can view a complete listing of the class named AdaptEngine02 in Listing 18 near the end of the lesson.

    AdaptEngine02 adapter = new AdaptEngine02(
                                filterLength,feedbackGain);

Listing 6

Although the class named AdaptEngine02 is new to this lesson, it is very similar to the class named AdaptEngine01 that has been used and explained in several of the earlier lessons referred to in the References section.  The only thing new in the AdaptEngine02 class is the ability to temporarily enable or disable the updating of the adaptive filter coefficients for each adaptive iteration by passing a boolean parameter to the adapt method.  Therefore, I won't provide a further explanation of the AdaptEngine02 class in this lesson.

Declare working objects and variables

Listing 7 declares and initializes some working objects and working variables that will be used later in the program.  This code is straightforward and should not require further explanation

    //Instantiate an array object that will be used as a
    // delay line for the wideband test data.
    double[] rawData = new double[pathOperator.length];
    
    //Instantiate a plotting object for four channels of
    // time-serie data.
    PlotALot05 timePlotObj = 
                   new PlotALot05("Time",468,148,25,2,0,0);

    //Instantiate a plotting object for two channels of
    // filter frequency response data.  One channel is for
    // the amplitude and the other channel is for the
    // phase.
    PlotALot03 freqPlotObj = 
                   new PlotALot03("Freq",264,487,35,2,0,0);

    //Instantiate a plotting object to display the filter
    // impulse response at specific time intervals during
    // the adaptive process.
    PlotALot01 filterPlotObj = new PlotALot01("Filter",
                     (filterLength * 4) + 8,487,70,4,0,0);

    //Declare and initialize working variables.
    double output = 0;
    double err = 0;
    double target = 0;
    double input = 0;
    double wbTestData = 0;
    boolean adaptOn;

Listing 7

Execute adaptive iterations

Listing 8 shows the beginning of a for loop that causes the program to execute the specified number of adaptive iterations.

    for(int cnt = 0;cnt < numberIterations;cnt++){

      adaptOn = true;

Listing 8

Enabling and disabling adaptive coefficient updates

The variable named adaptOn, which is set to true in Listing 8, is used to enable and disable the adaptive filter coefficient update process in the adapt method of the adaptive engine.  As you will see later, this variable is passed as the third parameter to the adapt method.

When the value of that parameter is true, the adapt method uses the adaptive error to update the coefficient values in the adaptive filter.  When that parameter is false, the coefficient values are not updated during that call to the adapt method.

Adaptive updates are disabled near the end of the run

The value of adaptOn is allowed to remain true during most of the adaptive iterations in this program.  However, near the end of the run, this value is set to false to disable the adaptive update and to freeze the adaptive filter in its current state.  Then two different impulses are inserted into the test data to produce the impulse responses shown in Figure 5, Figure 8, Figure 13, Figure 15, and Figure 19.

As with the use of the class named AdaptEngine02, this feature is new to this program, and I will explain how it is accomplished in more detail later.

Get wideband test data

Listing 9 gets and scales the next sample of wideband test data from a random number generator.  Before scaling by wbTestDataScale, the values are uniformly distributed from -1.0 to 1.0.

      wbTestData = 
                 wbTestDataScale*(2*(Math.random() - 0.5));

Listing 9

Insert an impulse in the wideband test data

Listing 10 sets the wideband input values near the end of the run to zero and turns adaptation off by setting the value of adaptOn to false.  Listing 10 also inserts an impulse into the wideband test data near the end of the run.

That impulse, which is shown on the red trace in the leftmost set of wavelets in Figure 19, produces the leftmost wavelets that appear in the other traces in Figure 19.  Among other things, this causes the impulse response of the path to be visible as the leftmost wavelet in the black trace in Figure 19.

      //Set wideband test data to zero
      if(cnt > (numberIterations - 5*filterLength)){
        wbTestData = 0;
        adaptOn = false;
      }//end if

      //Now insert an impulse at one specific location in
      // the wideband test data.
      if(cnt == numberIterations - 3*filterLength){
        wbTestData = 2 * wbTestDataScale;
      }//end if

Listing 10

Apply the path operator to the wideband test data

Listing 11 inserts the wideband test data into the rawData delay line and then invokes the reverseDotProduct method to perform one step in the convolution of the raw data with the path operator.

      //Insert the wideband test data into the raw data
      // delay line.
      flowLine(rawData,wbTestData);
    
      //Apply the path operator to the wideband test data.
      // Note the use of the method named
      // reverseDotProduct, which is new to this lesson.
      // This method provides the time reversal that is
      // necessary for true convolution.
      double pathData = 
                   reverseDotProduct(rawData,pathOperator);

Listing 11

The method named reverseDotProduct

Note that the method named reverseDotProduct is new to this lesson.  (You can view the lesson in its entirety in Listing 17.)  However, it is very similar to the method named dotProduct, which has been used in various previous lessons referred to in the section entitled References.  The only significant difference between this new version of the method named reverseDotProduct and the earlier version named dotProduct is that this new version reverses the time order of the operator coefficients before computing the vector dot product.  This is consistent with the requirement to reverse the time order of the operator coefficients before performing a convolution operation.

Because of the similarity of the new version of the method with the version that was used and explained in earlier lessons, I won't discuss the method named reverseDotProduct further in this lesson.

Declare another working variable

Listing 12 declares a variable of the AdaptiveResult class, which will receive a reference from the adapt method containing various results of the adaptive process.

      AdaptiveResult result = null;

Listing 12

Develop an identification filter

Listing 13 shows the beginning of an if statement that:

  • Selects between the development of an identification filter and an inverse filter based on a command-line parameter.
  • Establishes the appropriate input values to cause the adaptive engine to develop an identification filter.  (See The Identification Filter.  Values for an inverse filter are established later in the else clause of the statement).
  • Invokes the adapt method on the adaptive engine to perform one complete adaptive operation, returning the adaptive results in an object of type AdaptiveResult.
      if(identification){//Develop an identification filter
        //Establish input values.
        input = wbTestData;
        target = pathData;
        //Do the adaptive processing.
        result = adapter.adapt(input,target,adaptOn);

Listing 13

Develop an inverse filter

Listing 14 shows the else clause of the if statement that began in Listing 13.  The else clause:

  • Establishes the appropriate input values to cause the adaptive engine to develop an inverse filter.  (See The Inverse Filter.  Values for an identification filter were established in Listing 13).
  • Inserts an impulse in the path output data near the end of the run.
  • Invokes the adapt method on the adaptive engine to perform one complete adaptive operation, returning the adaptive results in an object of type AdaptiveResult.
      }else{//Develop an inverse filter.
        //Establish input values.
        input = pathData;
        target = wbTestData;

        //Insert an impulse into the pathData.
        if(cnt == numberIterations - filterLength){
          input = 2 * wbTestDataScale;
        }//end if

        //Do the adaptive processing
        result = adapter.adapt(input,target,adaptOn);
      }//end else

Listing 14

Inserting the impulse

When the run is nearly complete adaptive updates are disabled and the wideband input data values are set to zero by the code in Listing 10.  That code also inserts an impulse in the wideband test data.

Listing 14 inserts an impulse into the path output data values.  (This happens later than the point in time where the impulse was inserted into the wideband test data by the code in Listing 10.)  This impulse is shown in the black trace in the rightmost set of wavelets in Figure 19.  The existence of the impulse in the input to the adaptive filter causes the impulse response of the adaptive filter to be displayed as the rightmost wavelet in the blue trace in Figure 19.

Plot adaptive results, etc.

The code in Listing 15:

  • Gets and saves the adaptive results for plotting and spectral analysis.
  • Feeds the time series data to the plotting object.  This eventually results in graphs of the type shown in Figure 5 being plotted.
  • Computes and feeds summary results to the plotting objects every 400th iteration.  This eventually results in graphs of the type shown in Figure 6 being plotted.
  • Causes the data saved in the plotting objects to be plotted.
      //Get and save adaptive results for plotting and
      // spectral analysis
      output = result.output;
      err = result.err;
      filter = result.filterArray;
    
      //Feed the time series data to the plotting object.
      timePlotObj.feedData(input,target,output,-err);
    
      //Compute and plot summary results at the end of
      // every 400th iteration.
      if(cnt%400 == 0){
        displayFreqResponse(filter,freqPlotObj,
                                    128,filter.length - 1);

        //Display the filter impulse response coefficient
        // values.  The adaptive engine returns the filter
        // with the time axis reversed relative to the
        // conventional display of an impulse response.
        // Therefore, it is necessary to display it in
        // reverse order.
        for(int ctr = 0;ctr < filter.length;ctr++){
          filterPlotObj.feedData(
                       40*filter[filter.length - 1 - ctr]);
        }//end for loop
      }//End display of frequency response and filter
    }//End for loop,
    
    //Cause the data stored in the plotting objects to be
    // plotted.
    timePlotObj.plotData(376,0);//Top of screen
    freqPlotObj.plotData(0,148);//Left side of screen
    filterPlotObj.plotData(265,148);

Listing 15

If you have been studying the earlier lessons in this series referred to in the References section, you will find the code in Listing 15 to be very familiar and straightforward.

Convolve adaptive filter with the path operator

For the case of an inverse filter only, the code in Listing 16 convolves the adaptive filter with the impulse response of the path to determine the extent to which the filter is able to compensate for the characteristics of the path.

This determination is made by computing the amplitude and phase spectrum of the wavelet that is produced by the convolution, and displaying that frequency-domain information in the format shown in Figure 20.  If the filter is a true inverse filter for the path, the amplitude spectrum will be flat and the phase will be either zero or linear (indicating a time delay).  Deviations such as those shown in Figure 20 indicate that the inverse filter has some shortcomings.

    if(!identification){
      //Copy the filter into another array with zeros on
      // both ends to account for end effects in the
      // convolution process.
      double[] convInput = new double[
                    filter.length + 2*pathOperator.length];
      for(int cnt = 0;cnt < filter.length;cnt++){
        convInput[cnt + pathOperator.length] = filter[cnt];
      }//end for loop
      
      //Create an array to receive the convolution output.
      double[] convOutput = 
           new double[filter.length + pathOperator.length];

      //Call the method named convolve01 to perform the
      // convolution.
      convolve01(convInput,pathOperator,convOutput);

      //Now compute and plot the frequency spectrum of the
      // time series that results from convolving the final
      // adaptive filter with the path impulse response.
      // Ideally, the amplitude response will be flat from
      // zero to the folding frequency and the phase
      // response will be flat, or possibly linear
      // (indicating a time delay).
      
      //Instantiate a plotting object for two channels of
      // frequency response data.  One channel is for
      // the amplitude and the other channel is the phase.
      PlotALot03 finalFreqPlotObj = 
                  new PlotALot03("Final",264,137,35,2,0,0);
                     
      //Compute the frequency response and feed the results
      // to the plotting object.
      displayFreqResponse(convOutput,finalFreqPlotObj,
                                128,convOutput.length - 1);
                         
      //Cause the frequency response data stored in the
      // plotting object to be displayed on the screen.
      finalFreqPlotObj.plotData(
                           265 + filterLength * 4 + 8,148);
    }//end if

  }//end process method

Listing 16

Although the code in Listing 16 is rather long, with one exception it is straightforward and uses capabilities explained in earlier lessons in this series.  (See the section entitled References.)  Therefore, I won't explain the code in Listing 16 further in this lesson.

The convolve01 method

The one exception has to do with the use of the method named convolve01.  This method is very similar to a method that I presented and explained in an earlier lesson entitled Convolution and Frequency Filtering in Java, so I won't repeat that explanation here.  You can view the code for the method named convolve01 in Listing 17 near the end of the lesson.

Run the Program

I encourage you to copy the code from the classes in the section entitled Complete Program Listings.  Compile and execute the programs.  Experiment with the code.  Make changes to the code, recompile, execute, and observe the results of your changes.

Run all six of the built-in cases and see if you can explain the results.

After running the built-in cases, you might want to modify the filter coefficients for one of the path operators shown in Listing 3.  Then recompile and run the program and observe the results.  Did your change cause the results to be different?  If so, how were they different?  See if you can explain why they were different.

You might also try to come up with a set of path coefficients for which the adaptive process fails to converge, both for an identification filter and for an inverse filter.  If your changes cause either or both processes to fail to converge, see if you can explain the reasons why?

While you are at it, experiment with different values for feedBackGain.  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?

While experimenting with the feedbackGain, you might also want to experiment with the command-line parameter named numberIterations.  See if you can explain the results for small, medium, and large values for this parameter.

Other classes required

In addition to the classes named Adapt07, AdaptEngine02, and AdaptiveResult (for which the source code is provided in this lesson), you will need access to the following classes.  The source code for these classes can be found in the lessons indicated.

Summary

In this lesson, I showed you how to use a general-purpose LMS adaptive engine to write a Java program that illustrates adaptive identification filtering and adaptive inverse filtering for different path scenarios.

What's Next?

Adaptive filtering is commonly used for the following four scenarios:

  • System Identification
  • Inverse System Identification
  • Noise Cancellation
  • Prediction

This lesson explains how to write Java programs to work in the first two scenarios in the list.  I plan to publish lessons that explain and provide examples of the remaining two scenarios in future lessons.

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




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel