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 for (int thread = 0; thread < sNThreads; ++thread) {
39 mVal1DIDCs[thread] = fftwf_alloc_real(this->mRangeIDC);
40 mCoefficients[thread] = fftwf_alloc_complex(getNMaxCoefficients());
41 }
42 mFFTWPlan = fftwf_plan_dft_r2c_1d(this->mRangeIDC, mVal1DIDCs.front(), mCoefficients.front(), FFTW_ESTIMATE);
43}
44
45template <class Type>
47{
48 LOGP(info, "calculating fourier coefficients for current TF using naive approach using {} threads", sNThreads);
49
50 // check if IDCs are present for current side
51 if (this->getNIDCs() == 0) {
52 LOGP(warning, "no 1D-IDCs found!");
53 mFourierCoefficients.reset();
54 return;
55 }
56
57 const auto offsetIndex = this->getLastIntervals();
58
59 // see: https://en.wikipedia.org/wiki/Discrete_Fourier_transform#Definitiona
60 const bool add = mFourierCoefficients.getNCoefficientsPerTF() % 2;
61 const unsigned int lastCoeff = mFourierCoefficients.getNCoefficientsPerTF() / 2;
62
63#pragma omp parallel for num_threads(sNThreads)
64 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
65 const std::vector<float>& idcOneExpanded{this->getExpandedIDCOne()}; // 1D-IDC values which will be used for the FFT
66 for (unsigned int coeff = 0; coeff < lastCoeff; ++coeff) {
67 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * coeff); // index for storing real fourier coefficient
68 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
69 const float term0 = o2::constants::math::TwoPI * coeff / this->mRangeIDC;
70 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
71 const float term = term0 * index;
72 const float idc0 = idcOneExpanded[index + offsetIndex[interval]];
73 mFourierCoefficients(indexDataReal) += idc0 * std::cos(term);
74 mFourierCoefficients(indexDataImag) -= idc0 * std::sin(term);
75 }
76 }
77 if (add) {
78 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * lastCoeff); // index for storing real fourier coefficient
79 const float term0 = o2::constants::math::TwoPI * lastCoeff / this->mRangeIDC;
80 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
81 const float term = term0 * index;
82 const float idc0 = idcOneExpanded[index + offsetIndex[interval]];
83 mFourierCoefficients(indexDataReal) += idc0 * std::cos(term);
84 }
85 }
86 }
87 // normalize coefficient to number of used points
88 normalizeCoefficients();
89}
90
91template <class Type>
93{
94 LOGP(info, "calculating fourier coefficients for current TF using fftw3 using {} threads", sNThreads);
95
96 // for FFTW and OMP see: https://stackoverflow.com/questions/15012054/fftw-plan-creation-using-openmp
97 // check if IDCs are present for current side
98 if (this->getNIDCs() == 0) {
99 LOGP(warning, "no 1D-IDCs found!");
100 mFourierCoefficients.reset();
101 return;
102 }
103
104 const std::vector<unsigned int> offsetIndex = this->getLastIntervals();
105 const std::vector<float>& idcOneExpanded{this->getExpandedIDCOne()}; // 1D-IDC values which will be used for the FFT
106
107 if constexpr (std::is_same_v<Type, IDCFourierTransformBaseAggregator>) {
108#pragma omp parallel for num_threads(sNThreads)
109 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
110 fftwLoop(idcOneExpanded, offsetIndex, interval, omp_get_thread_num());
111 }
112 } else {
113 fftwLoop(idcOneExpanded, offsetIndex, 0, 0);
114 }
116 normalizeCoefficients();
117}
118
119template <class Type>
120inline 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)
121{
122 std::memcpy(mVal1DIDCs[thread], &idcOneExpanded[offsetIndex[interval]], this->mRangeIDC * sizeof(float)); // copy IDCs to avoid seg fault when using SIMD instructions
123 fftwf_execute_dft_r2c(mFFTWPlan, mVal1DIDCs[thread], mCoefficients[thread]); // perform ft
124 std::memcpy(&(*(mFourierCoefficients.mFourierCoefficients.begin() + mFourierCoefficients.getIndex(interval, 0))), mCoefficients[thread], mFourierCoefficients.getNCoefficientsPerTF() * sizeof(float)); // store coefficients
125}
126
127template <class Type>
128std::vector<std::vector<float>> o2::tpc::IDCFourierTransform<Type>::inverseFourierTransformNaive() const
129{
130 if (this->mRangeIDC % 2) {
131 LOGP(info, "number of specified fourier coefficients is {}, but should be an even number! FFTW3 method is used instead!", mFourierCoefficients.getNCoefficientsPerTF());
132 return inverseFourierTransformFFTW3();
133 }
134
135 // vector containing for each intervall the inverse fourier IDCs
136 std::vector<std::vector<float>> inverse(this->getNIntervals());
137 const float factor = o2::constants::math::TwoPI / this->mRangeIDC;
138
139 // loop over all the intervals. For each interval the coefficients are calculated
140 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
141 inverse[interval].resize(this->mRangeIDC);
142 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
143 const float term0 = factor * index;
144 unsigned int coeffTmp = 0;
145 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
146 for (unsigned int coeff = 0; coeff < this->mRangeIDC; ++coeff) {
147 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * coeffTmp); // index for storing real fourier coefficient
148 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
149 const float term = term0 * coeff;
150 inverse[interval][index] += mFourierCoefficients(indexDataReal) * std::cos(term) - fac * mFourierCoefficients(indexDataImag) * std::sin(term);
151 if (coeff < getNMaxCoefficients() - 1) {
152 ++coeffTmp;
153 } else {
154 --coeffTmp;
155 fac = -1;
156 };
157 }
158 }
159 }
160 return inverse;
161}
162
163template <class Type>
164std::vector<std::vector<float>> o2::tpc::IDCFourierTransform<Type>::inverseFourierTransformFFTW3() const
165{
166 // vector containing for each intervall the inverse fourier IDCs
167 std::vector<std::vector<float>> inverse(this->getNIntervals());
168
169 // loop over all the intervals. For each interval the coefficients are calculated
170 // this loop and execution of FFTW is not optimized as it is used only for debugging
171 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
172 inverse[interval].resize(this->mRangeIDC);
173 std::vector<std::array<float, 2>> val1DIDCs;
174 val1DIDCs.reserve(this->mRangeIDC);
175 for (unsigned int index = 0; index < getNMaxCoefficients(); ++index) {
176 const unsigned int indexDataReal = mFourierCoefficients.getIndex(interval, 2 * index); // index for storing real fourier coefficient
177 const unsigned int indexDataImag = indexDataReal + 1; // index for storing complex fourier coefficient
178 val1DIDCs.emplace_back(std::array<float, 2>{mFourierCoefficients(indexDataReal), mFourierCoefficients(indexDataImag)});
179 }
180 const fftwf_plan fftwPlan = fftwf_plan_dft_c2r_1d(this->mRangeIDC, reinterpret_cast<fftwf_complex*>(val1DIDCs.data()), inverse[interval].data(), FFTW_ESTIMATE);
181 fftwf_execute(fftwPlan);
182 fftwf_destroy_plan(fftwPlan);
183 }
184 return inverse;
185}
186
187template <class Type>
188void o2::tpc::IDCFourierTransform<Type>::dumpToFile(const char* outFileName, const char* outName) const
189{
190 TFile fOut(outFileName, "RECREATE");
191 fOut.WriteObject(this, outName);
192 fOut.Close();
193}
194
195template <class Type>
196void o2::tpc::IDCFourierTransform<Type>::dumpToTree(const char* outFileName) const
197{
198 o2::utils::TreeStreamRedirector pcstream(outFileName, "RECREATE");
199 pcstream.GetFile()->cd();
200 const std::vector<unsigned int> offsetIndex = this->getLastIntervals();
201 const auto idcOneExpanded = this->getExpandedIDCOne();
202 const auto inverseFourier = inverseFourierTransformNaive();
203 const auto inverseFourierFFTW3 = inverseFourierTransformFFTW3();
204
205 for (unsigned int interval = 0; interval < this->getNIntervals(); ++interval) {
206 std::vector<float> oneDIDCInverse = inverseFourier[interval];
207 std::vector<float> oneDIDCInverseFFTW3 = inverseFourierFFTW3[interval];
208
209 // get 1D-IDC values used for calculation of the fourier coefficients
210 std::vector<float> oneDIDC;
211 oneDIDC.reserve(this->mRangeIDC);
212 for (unsigned int index = 0; index < this->mRangeIDC; ++index) {
213 oneDIDC.emplace_back(idcOneExpanded[index + offsetIndex[interval]]);
214 }
215
216 for (unsigned int coeff = 0; coeff < mFourierCoefficients.getNCoefficientsPerTF(); ++coeff) {
217 float coefficient = mFourierCoefficients(mFourierCoefficients.getIndex(interval, coeff));
218
219 pcstream << "tree"
220 << "interval=" << interval
221 << "icoefficient=" << coeff // index of ith coefficient
222 << "coefficient=" << coefficient // value for ith coefficient
223 << "1DIDC.=" << oneDIDC
224 << "1DIDCiDFT.=" << oneDIDCInverse
225 << "1DIDCiDFTFFTW3.=" << oneDIDCInverseFFTW3
226 << "\n";
227 }
228 }
229}
230
231template <class Type>
233{
234 float* val1DIDCs = fftwf_alloc_real(this->mRangeIDC);
235 fftwf_complex* coefficients = fftwf_alloc_complex(getNMaxCoefficients());
236 fftwf_plan fftwPlan = fftwf_plan_dft_r2c_1d(this->mRangeIDC, val1DIDCs, coefficients, FFTW_ESTIMATE);
237 char* splan = fftwf_sprint_plan(fftwPlan);
238
239 LOGP(info, "========= printing FFTW plan ========= \n {}", splan);
240 double add = 0;
241 double mul = 0;
242 double fusedMultAdd = 0;
243 fftwf_flops(fftwPlan, &add, &mul, &fusedMultAdd);
244 LOGP(info, "additions: {} multiplications: {} fused multiply-add: {} sum: {}", add, mul, fusedMultAdd, add + mul + fusedMultAdd);
245
246 // free memory
247 free(splan);
248 fftwf_free(coefficients);
249 fftwf_free(val1DIDCs);
250 fftwf_destroy_plan(fftwPlan);
251}
252
253template <class Type>
254std::vector<std::pair<float, float>> o2::tpc::IDCFourierTransform<Type>::getFrequencies(const o2::tpc::FourierCoeff& coeff, const float samplingFrequency)
255{
256 std::vector<std::pair<float, float>> freq;
257 const auto nCoeff = coeff.getNCoefficientsPerTF();
258 const int nFreqPerInterval = nCoeff / 2;
259 const int nTFs = coeff.getNTimeFrames();
260 freq.reserve(nTFs * nFreqPerInterval);
261 for (int iTF = 0; iTF < nTFs; ++iTF) {
262 for (int iFreq = 0; iFreq < nFreqPerInterval; ++iFreq) {
263 const int realInd = nCoeff * iTF + iFreq * 2;
264 const int compInd = realInd + 1;
265 const float magnitude = std::sqrt(coeff(realInd) * coeff(realInd) + coeff(compInd) * coeff(compInd));
266 const float freqTmp = iFreq * samplingFrequency / nCoeff;
267 freq.emplace_back(freqTmp, magnitude);
268 }
269 }
270 return freq;
271}
272
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 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