Citlali
Loading...
Searching...
No Matches
sensitivity.h
Go to the documentation of this file.
1#pragma once
2
3#include <tuple>
4
5#include <Eigen/Core>
6#include <unsupported/Eigen/FFT>
7
8#include <tula/algorithm/mlinterp/mlinterp.hpp>
9#include <tula/algorithm/ei_stats.h>
10
12
13namespace internal {
14
15template <typename Derived>
16void smooth_edge_truncate(Eigen::DenseBase<Derived> &in, Eigen::DenseBase<Derived> &out, int w) {
17 Eigen::Index n_pts = in.size();
18
19 // ensure w is odd
20 if (w % 2 == 0) {
21 w++;
22 }
23
24 double w_inv = 1./w;
25 int w_mid = (w - 1)/2;
26
27 double sum;
28 for (Eigen::Index i=0; i<n_pts; i++) {
29 sum=0;
30 for (int j=0; j<w; j++) {
31 int add_index = i + j - w_mid;
32
33 if (add_index < 0) {
34 add_index=0;
35 }
36 else if (add_index > n_pts-1) {
37 add_index = n_pts-1;
38 }
39 sum += in(add_index);
40 }
41 out(i) = w_inv*sum;
42 }
43}
44
45template <typename T = Eigen::ArrayXd> T *ei_nullptr() {
46 return static_cast<T *>(nullptr);
47}
48
49template <typename Derived>
50using const_ref = typename Eigen::internal::ref_selector<Derived>::type;
51
52// non-const return type of derived()
53template <typename Derived>
54using ref = typename Eigen::internal::ref_selector<Derived>::non_const_type;
55
56// true if typename Derived manages its own data, e.g. MatrixXd, etc.
57// false if typename Derived is an expression.
58template <typename Derived>
60 : std::is_base_of<Eigen::PlainObjectBase<std::decay_t<Derived>>,
61 std::decay_t<Derived>> {};
62
63template <typename UnaryOp> struct MoveEnabledUnaryOp {
64 MoveEnabledUnaryOp(const UnaryOp &func = UnaryOp()) : m_func(func) {}
65
66 template <typename T, typename... Args>
67 decltype(auto) operator()(T &&in, Args &&... args) {
68 if constexpr (std::is_lvalue_reference<T>::value ||
70 // lvalue ref, either expression or non-expression
71 // return expression that builds on top of input
72 return m_func(std::forward<T>(in),
73 std::forward<decltype(args)>(args)...);
74 }
75 else {
76 // rvalue ref
77 // in this case we need to call the function and update inplace
78 // first and move to return
79 in = m_func(std::forward<T>(in),
80 std::forward<decltype(args)>(args)...);
81 return std::forward<T>(in);
82 }
83 }
84
85 protected:
86 const UnaryOp &m_func;
87};
88
89
90template <typename _Scalar>
91class Interval : public Eigen::Array<_Scalar, 2, 1> {
92 public:
93 using Scalar = _Scalar;
94 using Base = Eigen::Array<_Scalar, 2, 1>;
95 inline static const Scalar inf = std::numeric_limits<Scalar>::infinity();
96
97 Interval() : Base() {
98 this->operator[](0) = -inf;
99 this->operator[](1) = inf;
100 }
101 Interval(std::initializer_list<Scalar> interval) : Interval() {
102 if (interval.size() == 0)
103 return;
104 if (interval.size() == 2) {
105 auto it = interval.begin();
106 this->operator[](0) = *it;
107 ++it;
108 this->operator[](1) = *it;
109 return;
110 }
111 throw std::invalid_argument(
112 "empty initalize_list or {left, right} is required");
113 }
114
115 template <typename OtherDerived>
116 Interval(const Eigen::ArrayBase<OtherDerived> &other) : Base(other) {}
117
118 template <typename OtherDerived>
119 Interval &operator=(const Eigen::ArrayBase<OtherDerived> &other) {
120 this->Base::operator=(other);
121 return *this;
122 }
123 EIGEN_DEVICE_FUNC
124 EIGEN_STRONG_INLINE Scalar &left() { return this->x(); }
125 EIGEN_DEVICE_FUNC
126 EIGEN_STRONG_INLINE Scalar &right() { return this->y(); }
127};
128
129
130// npts nfreqs, dfreq
131using FreqStat = std::tuple<Eigen::Index, Eigen::Index, double>;
132
133FreqStat stat(Eigen::Index scanlength, double fsmp);
134Eigen::VectorXd freq(Eigen::Index npts, Eigen::Index nfreqs, double dfreq);
135
136enum Window {
138 Hanning = 1
140
141inline decltype(auto) hann(Eigen::Index npts) {
142 // hann window
143 // N numbers starting from 0 to (include) 2pi/N * (N-1)
144 // 0.5 - 0.5 cos([0, 2pi/N, 2pi/N * 2, ... 2pi/N * (N - 1)])
145 // NENBW = 1.5 * df therefore we devide by 1.5 here to get the
146 // equivalent scale as if no window is used
147 return (0.5 - 0.5 * Eigen::ArrayXd::LinSpaced(npts, 0, 2.0 * pi / npts * (npts - 1)).cos()).matrix() / 1.5;
148}
149
150// psd of individual scan npts/2 + 1 values with frequency [0, f/2];
151template <Window win=Hanning, typename DerivedA, typename DerivedB, typename DerivedC>
152FreqStat psd(const Eigen::DenseBase<DerivedA> &_scan,
153 Eigen::DenseBase<DerivedB> &psd, Eigen::DenseBase<DerivedC> *freqs,
154 double fsmp) {
155 // decltype(auto) scan = _scan.derived();
156 typename internal::const_ref<DerivedA> scan(_scan.derived());
157
158 auto stat = internal::stat(scan.size(), fsmp);
159 auto [npts, nfreqs, dfreq] = stat;
160
161 // prepare fft
162 Eigen::FFT<double> fft;
163 fft.SetFlag(Eigen::FFT<double>::HalfSpectrum);
164 fft.SetFlag(Eigen::FFT<double>::Unscaled);
165 Eigen::VectorXcd freqdata;
166
167 // branch according to whether applying hann
168 if constexpr (win == Hanning) {
169 //SPDLOG_INFO("apply hann window");
170 Eigen::VectorXd scns = scan.block(0,0,npts,1);
171 fft.fwd(freqdata, scns.cwiseProduct(
172 internal::hann(npts)).eval());
173 }
174
175 else if (win == NoWindow) {
176 fft.fwd(freqdata, scan.head(npts));
177 }
178
179 // calcualte psd and normalize to frequency resolution
180 psd = freqdata.cwiseAbs2() / dfreq / npts/ fsmp; // V/Hz^0.5
181 // accound for the negative frequencies by an extra factor of 2. note: first
182 // and last are 0 and nquist freq, so they only appear once
183 psd.segment(1, nfreqs - 2) *= 2.;
184
185 Eigen::VectorXd smoothed_psd(psd.size());
186 internal::smooth_edge_truncate(psd, smoothed_psd, 10);
187 psd = std::move(smoothed_psd);
188
189 // make the freqency array when requested
190 if (freqs) {
191 freqs->operator=(internal::freq(npts, nfreqs, dfreq));
192 }
193 return stat;
194}
195
196// a set of psd for multiple scans with potentialy different length.
197// all individual psds are interpolated to a common frequency array
198template <Window win=Hanning, typename DerivedA, typename DerivedB, typename DerivedC>
199FreqStat psds(const std::vector<DerivedA> &ptcs,
200 Eigen::DenseBase<DerivedB> &_psds,
201 Eigen::DenseBase<DerivedC> *freqs, double fsmp, Eigen::Index det) {
202
203 typename internal::ref<DerivedB> psds(_psds.derived());
204
205 // prepare common freq grid
206 Eigen::Index nscans = ptcs.size();
207 Eigen::Matrix<Eigen::Index,Eigen::Dynamic,1> scanlengths;
208
209 scanlengths.resize(nscans);
210 for(Eigen::Index j=0; j<nscans; j++) {
211 scanlengths(j) = ptcs[j].scans.data.rows();
212 }
213
214 // use the median length for computation
215 Eigen::Index len = tula::alg::median(scanlengths);
216
217 // get the common freq stat and freq array
218 auto stat = internal::stat(len, fsmp);
219 auto [npts, nfreqs, dfreq] = stat;
220 Eigen::VectorXd _freqs = internal::freq(npts, nfreqs, dfreq);
221
222 // compute psd for each scan and interpolate onto the common freq grid
223 psds.resize(nfreqs, nscans);
224
225 // make some temporaries
226 Eigen::VectorXd tfreqs, tpsd;
227 // need this to store array size for interp
228 Eigen::Matrix<Eigen::Index, Eigen::Dynamic,1> td(1);
229
230 // get the psds
231 for (Eigen::Index i=0; i<nscans; ++i) {
232 internal::psd<win>(ptcs[i].scans.data.block(0,det,scanlengths(i),1), tpsd,
233 &tfreqs, fsmp);
234 // interpolate (tfreqs, tpsd) on to _freqs
235 td << tfreqs.size();
236
237 // interp (tfreq, tpsd) onto freq and store the result in column i of psds
238 mlinterp::interp(td.data(), nfreqs,
239 tpsd.data(), psds.data() + i * nfreqs,
240 tfreqs.data(),_freqs.data());
241 }
242
243 // update the freqs array if requested
244 if (freqs) {
245 freqs->operator=(_freqs);
246 }
247 return stat;
248}
249
250// calc sens from psd
251inline auto psd2sen = internal::MoveEnabledUnaryOp([](auto&& psd) {
252 return (psd / 2.).cwiseSqrt(); // V * s^(1/2)
253});
254
255
256} // namespace internal
257
258template <typename tc_t, typename DerivedA, typename DerivedB>
260 std::vector<tc_t> &ptcs,
261 Eigen::DenseBase<DerivedA> &sensitivities, // V * s^(1/2)
262 Eigen::DenseBase<DerivedB> &noisefluxes, // V, = sqrt(\int PSD df)
263 double fsmp, Eigen::Index det, internal::Interval<double> freqrange = {3., 5.}) {
264
265 // get psds
266 Eigen::MatrixXd tpsds;
267 auto [npts, nfreqs, dfreq] = internal::psds<internal::Hanning>(
268 ptcs, tpsds, internal::ei_nullptr(), fsmp, det);
269
270 // compute noises by integrate over all frequencies
271 noisefluxes = (tpsds * dfreq).colwise().sum().cwiseSqrt();
272 auto meannoise = noisefluxes.mean();
273
274 // get sensitivity in V * s^(1/2)
275 Eigen::MatrixXd sens = internal::psd2sen(std::move(tpsds));
276
277 // compute sensitivity with given freqrange
278 // make use the fact that freqs = i * df to find Eigen::Index i
279 auto i0 = static_cast<Eigen::Index>(freqrange.left() / dfreq);
280 auto i1 = static_cast<Eigen::Index>(freqrange.right() / dfreq);
281
282 auto nf = i1 + 1 - i0;
283 sensitivities =
284 sens.block(i0, 0, nf, ptcs.size()).colwise().sum() / nf;
285}
Definition sensitivity.h:91
Eigen::Array< _Scalar, 2, 1 > Base
Definition sensitivity.h:94
Interval(const Eigen::ArrayBase< OtherDerived > &other)
Definition sensitivity.h:116
_Scalar Scalar
Definition sensitivity.h:93
Interval(std::initializer_list< Scalar > interval)
Definition sensitivity.h:101
Interval & operator=(const Eigen::ArrayBase< OtherDerived > &other)
Definition sensitivity.h:119
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar & left()
Definition sensitivity.h:124
Interval()
Definition sensitivity.h:97
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar & right()
Definition sensitivity.h:126
static const Scalar inf
Definition sensitivity.h:95
constexpr auto pi
Definition constants.h:6
Definition sensitivity.h:13
Window
Definition sensitivity.h:136
@ NoWindow
Definition sensitivity.h:137
@ Hanning
Definition sensitivity.h:138
T * ei_nullptr()
Definition sensitivity.h:45
Eigen::VectorXd freq(Eigen::Index npts, Eigen::Index nfreqs, double dfreq)
Definition sensitivity.cpp:20
std::tuple< Eigen::Index, Eigen::Index, double > FreqStat
Definition sensitivity.h:131
void smooth_edge_truncate(Eigen::DenseBase< Derived > &in, Eigen::DenseBase< Derived > &out, int w)
Definition sensitivity.h:16
FreqStat stat(Eigen::Index scanlength, double fsmp)
Definition sensitivity.cpp:8
FreqStat psds(const std::vector< DerivedA > &ptcs, Eigen::DenseBase< DerivedB > &_psds, Eigen::DenseBase< DerivedC > *freqs, double fsmp, Eigen::Index det)
Definition sensitivity.h:199
typename Eigen::internal::ref_selector< Derived >::non_const_type ref
Definition sensitivity.h:54
decltype(auto) hann(Eigen::Index npts)
Definition sensitivity.h:141
FreqStat psd(const Eigen::DenseBase< DerivedA > &_scan, Eigen::DenseBase< DerivedB > &psd, Eigen::DenseBase< DerivedC > *freqs, double fsmp)
Definition sensitivity.h:152
auto psd2sen
Definition sensitivity.h:251
typename Eigen::internal::ref_selector< Derived >::type const_ref
Definition sensitivity.h:50
void calc_sensitivity(std::vector< tc_t > &ptcs, Eigen::DenseBase< DerivedA > &sensitivities, Eigen::DenseBase< DerivedB > &noisefluxes, double fsmp, Eigen::Index det, internal::Interval< double > freqrange={3., 5.})
Definition sensitivity.h:259
Definition sensitivity.h:63
const UnaryOp & m_func
Definition sensitivity.h:86
MoveEnabledUnaryOp(const UnaryOp &func=UnaryOp())
Definition sensitivity.h:64
Definition sensitivity.h:61