Project
Loading...
Searching...
No Matches
IDCFourierTransform.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
15#include "Framework/Logger.h"
16#include "TFile.h"
17#include <fftw3.h>
18
19#if (defined(WITH_OPENMP) || defined(_OPENMP)) && !defined(__CLING__)
20#include <omp.h>
21#else
22static inline int omp_get_thread_num() { return 0; }
23#endif
24
25template <class Type>
27{
28 for (int thread = 0; thread < sNThreads; ++thread) {
29 fftwf_free(mVal1DIDCs[thread]);
30 fftwf_free(mCoefficients[thread]);
31 }
32 fftwf_destroy_plan(mFFTWPlan);
33}
34
35template <class Type>
37{
38 mVal1DIDCs.resize(sNThreads);
39 mCoefficients.resize(sNThreads);
40 for (int thread = 0; thread < sNThreads; ++thread) {
41 mVal1DIDCs[thread] = fftwf_alloc_real(this->mRangeIDC);
42 mCoefficients[thread] = fftwf_alloc_complex(getNMaxCoefficients());
43 }
44 mFFTWPlan = fftwf_plan_dft_r2c_1d(this->mRangeIDC, mVal1DIDCs.front(), mCoefficients.front(), FFTW_ESTIMATE);
45}
46
47template <class Type>
49{
50 LOGP(info, "calculating fourier coefficients for current TF using naive approach using {} threads", sNThreads);
51
52 // check if IDCs are present for current side
53 if (this->getNIDCs() == 0) {
54 LOGP(warning, "no 1D-IDCs found!");
55 mFourierCoefficients.reset();
56 return;
57 }
58
59 const auto offsetIndex = this->getLastIntervals();
60
61 // see: https://en.wikipedia.org/wiki/Discrete_Fourier_transform#Definitiona
62 const bool add = mFourierCoefficients.getNCoefficientsPerTF() % 2;
63 const unsigned int lastCoeff = mFourierCoefficients.getNCoefficientsPerTF() / 2;
64
65#pragma omp parallel for num_threads(sNThreads)
66 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
67 const std::vector<float>& idcOneExpanded{this->getExpandedIDCOne()}; // 1D-IDC values which will be used for the FFT
68 for (unsigned int coeff = 0; coeff < lastCoeff; ++coeff) {
69 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * coeff); // index for storing real fourier coefficient
70 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
71 const float term0 = o2::constants::math::TwoPI * coeff / this->mRangeIDC;
72 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
73 const float term = term0 * index;
74 const float idc0 = idcOneExpanded[index + offsetIndex[interval]];
75 mFourierCoefficients(indexDataReal) += idc0 * std::cos(term);
76 mFourierCoefficients(indexDataImag) -= idc0 * std::sin(term);
77 }
78 }
79 if (add) {
80 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * lastCoeff); // index for storing real fourier coefficient
81 const float term0 = o2::constants::math::TwoPI * lastCoeff / this->mRangeIDC;
82 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
83 const float term = term0 * index;
84 const float idc0 = idcOneExpanded[index + offsetIndex[interval]];
85 mFourierCoefficients(indexDataReal) += idc0 * std::cos(term);
86 }
87 }
88 }
89 // normalize coefficient to number of used points
90 normalizeCoefficients();
91}
92
93template <class Type>
95{
96 LOGP(info, "calculating fourier coefficients for current TF using fftw3 using {} threads", sNThreads);
97
98 // for FFTW and OMP see: https://stackoverflow.com/questions/15012054/fftw-plan-creation-using-openmp
99 // check if IDCs are present for current side
100 if (this->getNIDCs() == 0) {
101 LOGP(warning, "no 1D-IDCs found!");
102 mFourierCoefficients.reset();
103 return;
104 }
105
106 const std::vector<unsigned int> offsetIndex = this->getLastIntervals();
107 const std::vector<float>& idcOneExpanded{this->getExpandedIDCOne()}; // 1D-IDC values which will be used for the FFT
108
109 if constexpr (std::is_same_v<Type, IDCFourierTransformBaseAggregator>) {
110#pragma omp parallel for num_threads(sNThreads)
111 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
112 fftwLoop(idcOneExpanded, offsetIndex, interval, omp_get_thread_num());
114 } else {
115 fftwLoop(idcOneExpanded, offsetIndex, 0, 0);
117
118 normalizeCoefficients();
119}
120
121template <class Type>
122inline void o2::tpc::IDCFourierTransform<Type>::fftwLoop(const std::vector<float>& idcOneExpanded, const std::vector<unsigned int>& offsetIndex, const unsigned int interval, const unsigned int thread)
123{
124 std::memcpy(mVal1DIDCs[thread], &idcOneExpanded[offsetIndex[interval]], this->mRangeIDC * sizeof(float)); // copy IDCs to avoid seg fault when using SIMD instructions
125 fftwf_execute_dft_r2c(mFFTWPlan, mVal1DIDCs[thread], mCoefficients[thread]); // perform ft
126 std::memcpy(&(*(mFourierCoefficients.mFourierCoefficients.begin() + mFourierCoefficients.getIndex(interval, 0))), mCoefficients[thread], mFourierCoefficients.getNCoefficientsPerTF() * sizeof(float)); // store coefficients
127}
128
129template <class Type>
130std::vector<std::vector<float>> o2::tpc::IDCFourierTransform<Type>::inverseFourierTransformNaive() const
131{
132 if (this->mRangeIDC % 2) {
133 LOGP(info, "number of specified fourier coefficients is {}, but should be an even number! FFTW3 method is used instead!", mFourierCoefficients.getNCoefficientsPerTF());
134 return inverseFourierTransformFFTW3();
135 }
136
137 // vector containing for each intervall the inverse fourier IDCs
138 std::vector<std::vector<float>> inverse(this->getNIntervals());
139 const float factor = o2::constants::math::TwoPI / this->mRangeIDC;
140
141 // loop over all the intervals. For each interval the coefficients are calculated
142 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
143 inverse[interval].resize(this->mRangeIDC);
144 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
145 const float term0 = factor * index;
146 unsigned int coeffTmp = 0;
147 int fac = 1; // if input data is real (and it is) the coefficients are mirrored https://dsp.stackexchange.com/questions/4825/why-is-the-fft-mirrored
148 for (unsigned int coeff = 0; coeff < this->mRangeIDC; ++coeff) {
149 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * coeffTmp); // index for storing real fourier coefficient
150 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
151 const float term = term0 * coeff;
152 inverse[interval][index] += mFourierCoefficients(indexDataReal) * std::cos(term) - fac * mFourierCoefficients(indexDataImag) * std::sin(term);
153 if (coeff < getNMaxCoefficients() - 1) {
154 ++coeffTmp;
155 } else {
156 --coeffTmp;
157 fac = -1;
158 };
159 }
160 }
161 }
162 return inverse;
163}
164
165template <class Type>
166std::vector<std::vector<float>> o2::tpc::IDCFourierTransform<Type>::inverseFourierTransformFFTW3() const
167{
168 // vector containing for each intervall the inverse fourier IDCs
169 std::vector<std::vector<float>> inverse(this->getNIntervals());
170
171 // loop over all the intervals. For each interval the coefficients are calculated
172 // this loop and execution of FFTW is not optimized as it is used only for debugging
173 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
174 inverse[interval].resize(this->mRangeIDC);
175 std::vector<std::array<float, 2>> val1DIDCs;
176 val1DIDCs.reserve(this->mRangeIDC);
177 for (unsigned int index = 0; index < getNMaxCoefficients(); ++index) {
178 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * index); // index for storing real fourier coefficient
179 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
180 val1DIDCs.emplace_back(std::array<float, 2>{mFourierCoefficients(indexDataReal), mFourierCoefficients(indexDataImag)});
181 }
182 const fftwf_plan fftwPlan = fftwf_plan_dft_c2r_1d(this->mRangeIDC, reinterpret_cast<fftwf_complex*>(val1DIDCs.data()), inverse[interval].data(), FFTW_ESTIMATE);
183 fftwf_execute(fftwPlan);
184 fftwf_destroy_plan(fftwPlan);
185 }
186 return inverse;
187}
188
189template <class Type>
190void o2::tpc::IDCFourierTransform<Type>::dumpToFile(const char* outFileName, const char* outName) const
191{
192 TFile fOut(outFileName, "RECREATE");
193 fOut.WriteObject(this, outName);
194 fOut.Close();
195}
196
197template <class Type>
198void o2::tpc::IDCFourierTransform<Type>::dumpToTree(const char* outFileName) const
199{
200 o2::utils::TreeStreamRedirector pcstream(outFileName, "RECREATE");
201 pcstream.GetFile()->cd();
202 const std::vector<unsigned int> offsetIndex = this->getLastIntervals();
203 const auto idcOneExpanded = this->getExpandedIDCOne();
204 const auto inverseFourier = inverseFourierTransformNaive();
205 const auto inverseFourierFFTW3 = inverseFourierTransformFFTW3();
206
207 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
208 std::vector<float> oneDIDCInverse = inverseFourier[interval];
209 std::vector<float> oneDIDCInverseFFTW3 = inverseFourierFFTW3[interval];
210
211 // get 1D-IDC values used for calculation of the fourier coefficients
212 std::vector<float> oneDIDC;
213 oneDIDC.reserve(this->mRangeIDC);
214 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
215 oneDIDC.emplace_back(idcOneExpanded[index + offsetIndex[interval]]);
216 }
217
218 for (unsigned int coeff = 0; coeff < mFourierCoefficients.getNCoefficientsPerTF(); ++coeff) {
219 float coefficient = mFourierCoefficients(mFourierCoefficients.getIndex(interval, coeff));
220
221 pcstream << "tree"
222 << "interval=" << interval
223 << "icoefficient=" << coeff // index of ith coefficient
224 << "coefficient=" << coefficient // value for ith coefficient
225 << "1DIDC.=" << oneDIDC
226 << "1DIDCiDFT.=" << oneDIDCInverse
227 << "1DIDCiDFTFFTW3.=" << oneDIDCInverseFFTW3
228 << "\n";
229 }
230 }
231}
232
233template <class Type>
235{
236 float* val1DIDCs = fftwf_alloc_real(this->mRangeIDC);
237 fftwf_complex* coefficients = fftwf_alloc_complex(getNMaxCoefficients());
238 fftwf_plan fftwPlan = fftwf_plan_dft_r2c_1d(this->mRangeIDC, val1DIDCs, coefficients, FFTW_ESTIMATE);
239 char* splan = fftwf_sprint_plan(fftwPlan);
240
241 LOGP(info, "========= printing FFTW plan ========= \n {}", splan);
242 double add = 0;
243 double mul = 0;
244 double fusedMultAdd = 0;
245 fftwf_flops(fftwPlan, &add, &mul, &fusedMultAdd);
246 LOGP(info, "additions: {} multiplications: {} fused multiply-add: {} sum: {}", add, mul, fusedMultAdd, add + mul + fusedMultAdd);
247
248 // free memory
249 free(splan);
250 fftwf_free(coefficients);
251 fftwf_free(val1DIDCs);
252 fftwf_destroy_plan(fftwPlan);
253}
254
255template <class Type>
256std::vector<std::pair<float, float>> o2::tpc::IDCFourierTransform<Type>::getFrequencies(const o2::tpc::FourierCoeff& coeff, const float samplingFrequency)
257{
258 std::vector<std::pair<float, float>> freq;
259 const auto nCoeff = coeff.getNCoefficientsPerTF();
260 const int nFreqPerInterval = nCoeff / 2;
261 const int nTFs = coeff.getNTimeFrames();
262 freq.reserve(nTFs * nFreqPerInterval);
263 for (int iTF = 0; iTF < nTFs; ++iTF) {
264 for (int iFreq = 0; iFreq < nFreqPerInterval; ++iFreq) {
265 const int realInd = nCoeff * iTF + iFreq * 2;
266 const int compInd = realInd + 1;
267 const float magnitude = std::sqrt(coeff(realInd) * coeff(realInd) + coeff(compInd) * coeff(compInd));
268 const float freqTmp = iFreq * samplingFrequency / nCoeff;
269 freq.emplace_back(freqTmp, magnitude);
270 }
271 }
272 return freq;
273}
274
class for calculating the fourier coefficients from 1D-IDCs
fftwf_plan_s * fftwf_plan
float[2] fftwf_complex
useful math constants
void dumpToFile(const char *outFileName="Fourier.root", const char *outName="FourierCoefficients") const
void initFFTW3Members()
initalizing fftw members, e.g. when changing sNThreads via setNThreads after first initialization
void printFFTWPlan() const
printing information about the algorithms which are used by FFTW for debugging e.g....
void dumpToTree(const char *outFileName="FourierTree.root") const
std::vector< std::pair< float, float > > getFrequencies(const float samplingFrequency=getSamplingFrequencyIDCHz()) const
GLuint index
Definition glcorearb.h:781
constexpr float TwoPI
Interval< T > interval(const VerticalEdge< T > &edge)
struct containing the fourier coefficients calculated from IDC0 for n timeframes
unsigned int getNCoefficientsPerTF() const
unsigned int getNTimeFrames() const