The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
##-*- Mode: CPerl -*-

use strict;
use warnings;

our $VERSION = '0.002';
pp_setversion("'$VERSION'");

pp_addpm({At=>'Top'}, <<'EO_TOPMATTER');

=head1 NAME

PDL::DSP::Iir -- Infinite impulse response and recursive filters

=head1 DESCRIPTION

This module provides recursive filters. Currently, only
moving average filters are implemented. The moving average
is actually a FIR, but it is implemented recursively as are
IIR filters, so it is included here.

=head1 SYNOPSIS

 use PDL::LiteF;
 use PDL::DSP::Iir( 'moving_average' );

 # apply three passes of a moving average with window size 2*5+1 = 11.
 $filtered_data = moving_average($data,5,3);

 # apply one pass of a moving average with window size 2*3+1 = 7.
 $filtered_data = moving_average($data,3);

 # call as method with window size 2 * $hw + 1
  $y = $x->moving_average($hw);

=cut

$PDL::onlinedoc->scan(__FILE__) if $PDL::onlinedoc;

use strict;
use warnings;

use PDL::LiteF;
use PDL::NiceSlice;
use PDL::Options;

EO_TOPMATTER

pp_addpm({At => 'Middle'}, <<'EO_MIDDLE');

=head2

The function C<moving_average> calls one of the lower level functions
C<mov_avg> or C<multi_pass_mov_avg>, but they can be used directly as
well.

=head2  moving_average

=for ref

 moving_average($data, $half_width [, $n_passes ]);

Other terms for this kind of filter are: smoothing, sliding average, box smoothing, boxcar smoothing,
boxcar filter, etc.

This function applies a moving average of C< $data > with window of size C<w = 2*$half_width+1>. That is, the 
output value of point C<i> is the (uniformly weighted) average of the input points from
C<i - half_width> through C<i + half_width>. The filter is
repeated C<$n_passes> times if C<$n_passes> is supplied. This effectively applies a filter
with a response that decreases with the distance from the point C<i>.  The recursive
algorithm is used, which can be much faster than the equivalent direct convolution
with a rectangular window. The boundary at C<data[0]> is treated by using a window
of size 1 at C<data[0]>, then of size 3 at C<data[1]>, and so on until the size reaches
C<w>. The boundary at C<datap[n-1]> is treated in the same way. In this way, the response
around each point is symmetric.

The accumulator for all fixed point types is of type C<int>, and for both floating point types is C<double>.

=cut

sub moving_average {
    my($x,$hw,$passes) = @_;
    $passes ? multi_pass_mov_avg($x,$hw,$passes) :
        mov_avg($x,$hw);
}

*PDL::moving_average = \&moving_average;

EO_MIDDLE


#our @METHODS = qw( moving_average mov_avg multi_pass_mov_avg );
#pp_add_exported ( @METHODS );

pp_add_exported qw( moving_average mov_avg multi_pass_mov_avg );

#pp_addpm "our \@METHODS = qw(@METHODS);\n";

# generate code to make one pass of moving average.
# $x is source pdl, $y is target pdl.
# eg 'x' and 'y',  or 'y' and 'ytemp'.
sub gen_onepass {
    my ($x,$y) = @_;
    qq{
     {
       acc = \$$y(n=>0) = \$$x(n=>0);
       for(i=1;i<=hw;i++) {
         acc += \$$x(n=>2*i-1) + \$$x(n=>2*i);
         \$$y(n=>i) = acc /(2*i+1);
       }
       for(i=hw+1;i<nmax-hw;i++) {
         acc += \$$x(n=>i+hw) - \$$x(n=>i-hw-1);
         \$$y(n=>i) = acc/w;
       }
       for(i=nmax-hw;i<nmax;i++) {
         acc -= \$$x(n=>2*i-nmax) + \$$x(n=>2*i-nmax-1);
         \$$y(n=>i) = acc/(2*(nmax-i)-1);
       }
      }
     }
}

{ # moving average block

 my $code0 = q{
   $TFDBSUL(double,double,int,int,int,int) acc = 0;
   int i, w, nmax, max, hw;
   hw = $COMP(half_width);
   w = 2 * hw + 1;
   nmax = $SIZE(n);
 };

 my $gen_types = [qw(F D B S U L)];

pp_def('mov_avg',
  Pars  => 'x(n); double [o]y(n)',
  OtherPars => 'int half_width',
  GenericTypes => $gen_types,
  HandleBad => 0,
  Code  => 
   $code0 . gen_onepass('x','y'),
   Doc => q{

=for ref

Moving average with a single pass.

=cut

}
);

pp_def('multi_pass_mov_avg',
  Pars  => 'x(n); double [o]y(n); double [t]ytemp(n)',
  OtherPars => 'int half_width; int n_passes',
  GenericTypes => $gen_types,
  HandleBad => 0,
  Code  => 
   $code0 . q{
   int npass = $COMP(n_passes);
   int ipass;
   int parity = npass % 2;
   if (parity)
      } . gen_onepass('x','y') . q!
   else 
      ! . gen_onepass('x','ytemp') . q!
   for(ipass=1;ipass<npass;ipass++) {
     if( (ipass+parity) % 2 )
      ! . gen_onepass('ytemp','y') . q!
    else
      ! . gen_onepass('y','ytemp') . q!
   }
  !,   
 Doc => q{

=for ref

This is the same as C<mov_avg>, except that
smoothing is repeated n_passes times. Note that storage C<ytemp>
is created.

=cut

}

 );

}# end moving average block