File: digitalFilter2FrequencyResponse.cpp

package info (click to toggle)
groops 0%2Bgit20250907%2Bds-1
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 11,140 kB
  • sloc: cpp: 135,607; fortran: 1,603; makefile: 20
file content (146 lines) | stat: -rw-r--r-- 5,269 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/***********************************************/
/**
* @file digitalFilter2FrequencyResponse.cpp
*
* @brief Amplitude and phase response of a filter cascade
*
* @author Andreas Kvas
* @date 2017-02-01
*/
/***********************************************/

// Latex documentation
#define DOCSTRING docstring
static const char *docstring = R"(
Compute amplitude-, phase-, group delay and frequency response of a \configClass{digitalFilter}{digitalFilterType} cascade.
The \configFile{outputfileResponse}{matrix} is a matrix with following columns:
freq $[Hz]$, ampl, phase $[rad]$, group delay $[-]$, real, imag.

When \config{unwrapPhase} is set to true, $2\pi$ jumps of the phase response are removed before writing the output to file.

The response of the filter cascade is given by the product of each individual frequency response:
\begin{equation}
  H(f) = \prod_f H_j(f).
\end{equation}
Amplitude and phase response are computed from the frequency response via
\begin{equation}
  A(f) = |H(f)| \hspace{5pt}\text{and}\hspace{5pt} \Phi(f) = \arctan \frac{\mathcal{I}(H(f))}{\mathcal{R}(H(f))}.
\end{equation}
The group delay is computed by numerically differentiating the phase response
\begin{equation}
  \tau_g(f_k) = \frac{1}{2} \left[\frac{\Phi(f_k) - \Phi(f_{k-1})}{2\pi(f_k-f_{k-1})} + \frac{\Phi(f_{k+1}) - \Phi(f_{k})}{2\pi(f_{k+1}-f_{k})}\right] \approx \frac{d\Phi}{df}\frac{df}{d\omega}.
\end{equation}
The frequency vector for a \config{length} $N$ and a \config{sampling} $\Delta t$ is given by
\begin{equation}
  f_k = \frac{k}{N \Delta t}, \hspace{15pt} k \in \{0, \dots, \left\lfloor\frac{N+2}{2}\right\rfloor-1\}.
\end{equation}

See also \program{DigitalFilter2ImpulseResponse}.
)";

/***********************************************/

#include "programs/program.h"
#include "base/fourier.h"
#include "files/fileMatrix.h"
#include "classes/digitalFilter/digitalFilter.h"

/***** CLASS ***********************************/

/** @brief Amplitude and phase response of a filter cascade.
* @ingroup programsGroup */
class DigitalFilter2FrequencyResponse
{
private:
  Vector groupDelay(const Vector &f, const Vector &phase);
  Vector unwrap(const Vector &phase);
public:
  void run(Config &config, Parallel::CommunicatorPtr comm);
};

GROOPS_REGISTER_PROGRAM(DigitalFilter2FrequencyResponse, SINGLEPROCESS, "amplitude and phase response of a filter cascade", Misc)

/***********************************************/

void DigitalFilter2FrequencyResponse::run(Config &config, Parallel::CommunicatorPtr /*comm*/)
{
  try
  {
    FileName         fileNameOut;
    UInt             length;
    Double           sampling;
    DigitalFilterPtr filter;
    Bool             skipZero = FALSE;
    Bool             unwrapPhase = FALSE;

    readConfig(config, "outputfileResponse", fileNameOut, Config::MUSTSET,  "",    "columns: freq [Hz], ampl, phase [rad], group delay [-], real, imag");
    readConfig(config, "digitalFilter",      filter,      Config::MUSTSET,  "",    "");
    readConfig(config, "length",             length,      Config::DEFAULT,  "512", "length of the data series in time domain");
    readConfig(config, "sampling",           sampling,    Config::DEFAULT,  "1.0", "sampling to determine frequency [seconds]");
    readConfig(config, "skipZeroFrequency",  skipZero,    Config::DEFAULT,  "0",   "omit zero frequency when writing to file");
    readConfig(config, "unwrapPhase",        unwrapPhase, Config::DEFAULT,  "0",   "unwrap phase response");
    if(isCreateSchema(config)) return;

    logStatus<<"compute frequency response"<<Log::endl;
    Matrix A((length+2)/2, 6);
    copy(Fourier::frequencies(length, sampling), A.column(0));

    auto F = filter->frequencyResponse(length);
    Vector amplitude, phase;
    Fourier::complex2AmplitudePhase(F, amplitude, phase);
    Vector unwrappedPhase = unwrap(phase);
    Vector grpDel = groupDelay(A.column(0), unwrappedPhase);
    if(unwrapPhase)
      phase = unwrappedPhase;

    copy(amplitude,  A.column(1));
    copy(phase,      A.column(2));
    copy(grpDel,     A.column(3));
    for(UInt i=0; i<A.rows(); i++)
    {
      A(i, 4) = F.at(i).real();
      A(i, 5) = F.at(i).imag();
    }

    logStatus<<"write response to <"<<fileNameOut<<">"<<Log::endl;
    writeFileMatrix(fileNameOut, A);
  }
  catch(std::exception &e)
  {
    GROOPS_RETHROW(e)
  }
}

/***********************************************/

Vector DigitalFilter2FrequencyResponse::unwrap(const Vector &phase)
{
  Vector uPhase = phase;

  for(UInt n = 1; n<uPhase.rows(); n++)
  {
    while( (uPhase(n) - uPhase(n-1)) <= PI)
      uPhase(n) += 2*PI;

    while( (uPhase(n) - uPhase(n-1)) >= PI)
      uPhase(n) -= 2*PI;
  }

  return uPhase;
}

/***********************************************/

Vector DigitalFilter2FrequencyResponse::groupDelay(const Vector &f, const Vector &uPhase)
{
  Vector grpDel(uPhase.rows());

  grpDel(0) = -(uPhase(1)-uPhase(0))/(f(1)-f(0));
  for(UInt n = 1; n<uPhase.rows()-1; n++)
    grpDel(n) = -( (uPhase(n)-uPhase(n-1))/(f(n)-f(n-1)) + (uPhase(n+1)-uPhase(n))/(f(n+1)-f(n)))*0.5;
  grpDel(grpDel.rows()-1) = -(uPhase(grpDel.rows()-1)-uPhase(grpDel.rows()-2))/(f(grpDel.rows()-1)-f(grpDel.rows()-2));

  return grpDel/(2*PI);
}

/***********************************************/