EmpiricalNoiseModel.cxx
Go to the documentation of this file.
1 
3 
4 
6 #include "WireCellUtil/Persist.h"
8 #include "WireCellUtil/Response.h" // fixme: should remove direct dependency
9 
12 
13 #include <iostream> // debug
14 
17 
18 
19 using namespace WireCell;
20 
21 Gen::EmpiricalNoiseModel::EmpiricalNoiseModel(const std::string& spectra_file,
22  const int nsamples,
23  const double period,
24  const double wire_length_scale,
25  // const double time_scale,
26  // const double gain_scale,
27  // const double freq_scale,
28  const std::string anode_tn,
29  const std::string chanstat_tn)
30  : m_spectra_file(spectra_file)
31  , m_nsamples(nsamples)
32  , m_period(period)
33  , m_wlres(wire_length_scale)
34  // , m_tres(time_scale)
35  // , m_gres(gain_scale)
36  // , m_fres(freq_scale)
37  , m_anode_tn(anode_tn)
38  , m_chanstat_tn(chanstat_tn)
39  , m_amp_cache(4)
40  , log(Log::logger("sim"))
41 {
42  m_fft_length = fft_best_length(m_nsamples);
43  // m_fft_length = m_nsamples;
44  gen_elec_resp_default();
45 }
46 
47 
48 Gen::EmpiricalNoiseModel::~EmpiricalNoiseModel()
49 {
50 }
51 
52 void Gen::EmpiricalNoiseModel::gen_elec_resp_default(){
53  // double shaping[5]={1,1.1,2,2.2,3}; // us
54 
55  // calculate the frequencies ...
56  m_elec_resp_freq.resize(m_fft_length,0);
57  for (unsigned int i=0;i!=m_elec_resp_freq.size();i++){
58  if (i<=m_elec_resp_freq.size()/2.){
59  m_elec_resp_freq.at(i) = i / (m_elec_resp_freq.size() *1.0) * 1./m_period ; // the second half is useless ...
60  }else{
61  m_elec_resp_freq.at(i) = (m_elec_resp_freq.size()-i) / (m_elec_resp_freq.size() *1.0) * 1./m_period ; // the second half is useless ...
62  }
63 
64  // if (m_elec_resp_freq.at(i) > 1./m_period / 2.){
65  // m_elec_resp_freq.resize(i);
66  // break;
67  // }
68  }
69 
70  // for (int i=0;i!=5;i++){
71  // Response::ColdElec elec_resp(1, shaping[i]); // default at 1 mV/fC
72  // auto sig = elec_resp.generate(WireCell::Waveform::Domain(0, m_fft_length*m_period), m_fft_length);
73  // auto filt = Waveform::dft(sig);
74  // int nconfig = shaping[i]/ 0.1;
75  // auto ele_resp_amp = Waveform::magnitude(filt);
76  // m_elec_resp_cache[nconfig] = ele_resp_amp;
77  // }
78 }
79 
80 WireCell::Configuration Gen::EmpiricalNoiseModel::default_configuration() const
81 {
83 
84  /// The file holding the spectral data
85  cfg["spectra_file"] = m_spectra_file;
86  cfg["nsamples"] = m_nsamples; // number of samples up to Nyquist frequency
87  cfg["period"] = m_period;
88  cfg["wire_length_scale"] = m_wlres; //
89  // cfg["time_scale"] = m_tres;
90  // cfg["gain_scale"] = m_gres;
91  // cfg["freq_scale"] = m_fres;
92  cfg["anode"] = m_anode_tn; // name of IAnodePlane component
93 
94  return cfg;
95 }
96 
98 {
99 
100  if (spectrum.nsamples == m_fft_length && spectrum.period == m_period) {
101  return;
102  }
103 
104  // scale the amplitude ...
105  double scale = sqrt(m_fft_length/(spectrum.nsamples*1.0) * spectrum.period / (m_period*1.0));
106  spectrum.constant *= scale;
107  for (unsigned int ind = 0; ind!=spectrum.amps.size(); ind++){
108  spectrum.amps[ind] *= scale;
109  }
110 
111  // interpolate ...
112 
113  amplitude_t temp_amplitudes(m_fft_length,0);
114  int count_low = 0;
115  int count_high = 1;
116  double mu=0;
117  for (int i=0;i!=m_fft_length;i++){
118  double frequency = m_elec_resp_freq.at(i);
119  if (frequency <= spectrum.freqs[0]){
120  count_low = 0;
121  count_high = 1;
122  mu = 0;
123  }else if (frequency >= spectrum.freqs.back()){
124  count_low = spectrum.freqs.size()-2;
125  count_high = spectrum.freqs.size()-1;
126  mu = 1;
127  }else{
128  for (unsigned int j=0;j!=spectrum.freqs.size();j++){
129  if (frequency>spectrum.freqs.at(j)){
130  count_low = j;
131  count_high = j+1;
132  }else{
133  break;
134  }
135  }
136  mu = (frequency - spectrum.freqs.at(count_low)) / (spectrum.freqs.at(count_high)-spectrum.freqs.at(count_low));
137  }
138 
139 
140  temp_amplitudes.at(i) = (1-mu) * spectrum.amps[count_low] + mu * spectrum.amps[count_high];
141 
142  }
143 
144  spectrum.amps = temp_amplitudes;
145  spectrum.amps.push_back(spectrum.constant);
146 
147  return;
148 }
149 
151 {
152  m_anode_tn = get(cfg, "anode", m_anode_tn);
153  m_anode = Factory::find_tn<IAnodePlane>(m_anode_tn);
154 
155  m_chanstat_tn = get(cfg, "chanstat", m_chanstat_tn);
156  if (m_chanstat_tn != "") { // allow for an empty channel status, no deviation from norm
157  m_chanstat = Factory::find_tn<IChannelStatus>(m_chanstat_tn);
158  }
159 
160  log->debug("EmpiricalNoiseModel: using anode {}, chanstat {}", m_anode_tn, m_chanstat_tn);
161 
162  m_spectra_file = get(cfg, "spectra_file", m_spectra_file);
163 
164  m_nsamples = get(cfg, "nsamples", m_nsamples);
165  m_fft_length = fft_best_length(m_nsamples);
166  //m_fft_length = m_nsamples;
167  m_period = get(cfg, "period", m_period);
168  m_wlres = get(cfg, "wire_length_scale", m_wlres);
169  // m_tres = get(cfg, "time_scale", m_tres);
170  // m_gres = get(cfg, "gain_scale", m_gres);
171  // m_fres = get(cfg, "freq_scale", m_fres);
172 
173  // Load the data file assuming. The file should be a list of
174  // dictionaries matching the
175  // wirecell.sigproc.noise.schema.NoiseSpectrum class. Fixme:
176  // should break out this code separate.
177  auto jdat = Persist::load(m_spectra_file);
178  const int nentries = jdat.size();
179  m_spectral_data.clear();
180 
181  gen_elec_resp_default();
182 
183  for (int ientry=0; ientry<nentries; ++ientry) {
184  auto jentry = jdat[ientry];
185  NoiseSpectrum *nsptr = new NoiseSpectrum();
186 
187  nsptr->plane = jentry["plane"].asInt();
188  nsptr->nsamples = jentry["nsamples"].asInt();
189  nsptr->period = jentry["period"].asFloat();// * m_tres;
190  nsptr->gain = jentry["gain"].asFloat();// *m_gres;
191  nsptr->shaping = jentry["shaping"].asFloat();// * m_tres;
192  nsptr->wirelen = jentry["wirelen"].asFloat();// * m_wlres;
193  nsptr->constant = jentry["const"].asFloat();
194 
195  auto jfreqs = jentry["freqs"];
196  const int nfreqs = jfreqs.size();
197  nsptr->freqs.resize(nfreqs, 0.0);
198  for (int ind=0; ind<nfreqs; ++ind) {
199  nsptr->freqs[ind] = jfreqs[ind].asFloat();// * m_fres;
200  }
201  auto jamps = jentry["amps"];
202  const int namps = jamps.size();
203  nsptr->amps.resize(namps+1, 0.0);
204  for (int ind=0; ind<namps; ++ind) {
205  nsptr->amps[ind] = jamps[ind].asFloat();
206  }
207  // put the constant term at the end of the amplitude ...
208  // nsptr->amps[namps] = nsptr->constant;
209 
210  resample(*nsptr);
211  m_spectral_data[nsptr->plane].push_back(nsptr); // assumes ordered by wire length!
212  }
213 }
214 
215 
217 {
218  auto it = m_spectral_data.find(iplane);
219  if (it == m_spectral_data.end()) {
220  return amplitude_t();
221  }
222 
223  const auto& spectra = it->second;
224 
225 
226 
227  // Linearly interpolate spectral data to wire length.
228 
229  // Any wire lengths
230  // which are out of bounds causes the nearest result to be
231  // returned (flat extrapolation)
232  const NoiseSpectrum* front = spectra.front();
233  if (wire_length <= front->wirelen) {
234  return front->amps;
235  }
236  const NoiseSpectrum* back = spectra.back();
237  if (wire_length >= back->wirelen) {
238  return back->amps;
239  }
240 
241  const int nspectra = spectra.size();
242  for (int ind=1; ind<nspectra; ++ind) {
243  const NoiseSpectrum* hi = spectra[ind];
244  if (hi->wirelen < wire_length) {
245  continue;
246  }
247  const NoiseSpectrum* lo = spectra[ind-1];
248 
249  const double delta = hi->wirelen - lo->wirelen;
250  const double dist = wire_length - lo->wirelen;
251  const double mu = dist/delta;
252 
253  const int nsamp = lo->amps.size();
254  amplitude_t amp(nsamp);
255  for (int isamp=0; isamp<nsamp; ++isamp) { // regular amplitude ...
256  amp[isamp] = lo->amps[isamp]*(1-mu) + hi->amps[isamp]*mu;
257  }
258  // amp[nsamp] = lo->constant*(1-mu) + hi->constant*mu; // do the constant term ...
259  return amp;
260  }
261  // should not get here
262  return amplitude_t();
263 
264 }
265 
266 const std::vector<float>& Gen::EmpiricalNoiseModel::freq() const
267 {
268  // assume frequency is universal ...
269  const int iplane = 0;
270  auto it = m_spectral_data.find(iplane);
271  const auto& spectra = it->second;
272  return spectra.front()->freqs;
273 }
274 
275 const double Gen::EmpiricalNoiseModel::shaping_time(int chid) const
276 {
277  auto wpid = m_anode->resolve(chid);
278  const int iplane = wpid.index();
279  auto it = m_spectral_data.find(iplane);
280  const auto& spectra = it->second;
281  return spectra.front()->shaping;
282 }
283 
284 const double Gen::EmpiricalNoiseModel::gain(int chid) const
285 {
286  auto wpid = m_anode->resolve(chid);
287  const int iplane = wpid.index();
288  auto it = m_spectral_data.find(iplane);
289  const auto& spectra = it->second;
290  return spectra.front()->gain;
291 }
292 
293 
294 
295 const IChannelSpectrum::amplitude_t& Gen::EmpiricalNoiseModel::operator()(int chid) const
296 {
297  // get truncated wire length for cache
298  int ilen=0;
299  auto chlen = m_chid_to_intlen.find(chid);
300  if (chlen == m_chid_to_intlen.end()) { // new channel
301  auto wires = m_anode->wires(chid); // sum up wire length
302  double len = 0.0;
303  for (auto wire : wires) {
304  len += ray_length(wire->ray());
305  }
306  // cache every cm ...
307  ilen = int(len/m_wlres);
308  // there might be aproblem with the wire length's unit
309  //ilen = int(len);
310  m_chid_to_intlen[chid] = ilen;
311  }
312  else {
313  ilen = chlen->second;
314  }
315 
316 
317  // saved content
318  auto wpid = m_anode->resolve(chid);
319  const int iplane = wpid.index();
320 
321  auto& amp_cache = m_amp_cache.at(iplane);
322  auto lenamp = amp_cache.find(ilen);
323  if (lenamp == amp_cache.end()) {
324  auto amp = interpolate(iplane, ilen*m_wlres); // interpolate ...
325  amp_cache[ilen] = amp;
326  }
327  lenamp = amp_cache.find(ilen);
328 
329  // prepare to scale ...
330  double db_gain = gain(chid);
331  double db_shaping = shaping_time(chid);
332 
333  // get channel gain
334  //float ch_gain = 14 * units::mV/units::fC, ch_shaping = 2.0 * units::us;
335 
336  double ch_gain = db_gain, ch_shaping = db_shaping;
337  if (m_chanstat) { // allow for deviation from nominal
338  ch_gain = m_chanstat->preamp_gain(chid);
339  ch_shaping = m_chanstat->preamp_shaping(chid);
340  }
341  double constant = lenamp->second.back();
342  int nbin = lenamp->second.size()-1;
343 
344  // amplitude_t comb_amp;
345  comb_amp = lenamp->second;
346  comb_amp.pop_back();
347 
348  if (fabs(ch_gain - db_gain ) > 0.01 * ch_gain){
349  // scale the amplitude, not the constant term ...
350  for (int i=0;i!=nbin;i++){
351  comb_amp.at(i) *= ch_gain/db_gain;
352  }
353  }
354 
355  if (fabs(ch_shaping-db_shaping) > 0.01*ch_shaping){
356  // scale the amplitude by different shaping time ...
357  int nconfig = ch_shaping/units::us/0.1;
358  auto resp1 = m_elec_resp_cache.find(nconfig);
359  if (resp1 == m_elec_resp_cache.end()){
360 
361  Response::ColdElec elec_resp(10, ch_shaping); // default at 1 mV/fC
362  auto sig = elec_resp.generate(WireCell::Waveform::Domain(0, m_fft_length*m_period), m_fft_length);
363  auto filt = Waveform::dft(sig);
364  auto ele_resp_amp = Waveform::magnitude(filt);
365 
366  ele_resp_amp.resize(m_elec_resp_freq.size());
367  m_elec_resp_cache[nconfig] = ele_resp_amp;
368 
369  }
370  resp1 = m_elec_resp_cache.find(nconfig);
371 
372  nconfig = db_shaping/units::us/0.1;
373  auto resp2 = m_elec_resp_cache.find(nconfig);
374  if (resp2 == m_elec_resp_cache.end()){
375  Response::ColdElec elec_resp(10, db_shaping); // default at 1 mV/fC
376  auto sig = elec_resp.generate(WireCell::Waveform::Domain(0, m_fft_length*m_period), m_fft_length);
377  auto filt = Waveform::dft(sig);
378  auto ele_resp_amp = Waveform::magnitude(filt);
379 
380  ele_resp_amp.resize(m_elec_resp_freq.size());
381  m_elec_resp_cache[nconfig] = ele_resp_amp;
382  }
383  resp2 = m_elec_resp_cache.find(nconfig);
384 
385  for (int i=0;i!=nbin;i++){
386  comb_amp.at(i) *= resp1->second.at(i)/resp2->second.at(i);
387  }
388 
389  }
390 
391 
392  // add the constant terms ...
393  for (int i=0;i!=nbin;i++){
394  comb_amp.at(i) = sqrt(pow(comb_amp.at(i),2) + pow(constant,2)); // units still in mV
395  }
396 
397  return comb_amp;
398 }
399 
400 
401 
std::vector< float > amplitude_t
constexpr T pow(T x)
Definition: pow.h:72
std::size_t fft_best_length(size_t nsamples, bool keep_odd_even=false)
STL namespace.
cfg
Definition: dbjson.py:29
A functional object caching gain and shape.
Definition: Response.h:165
std::pair< double, double > Domain
Definition: Waveform.h:75
def configure(cfg)
Definition: cuda.py:34
double ray_length(const Ray &ray)
Definition: Point.cxx:62
WireCell::Waveform::realseq_t generate(const WireCell::Waveform::Domain &domain, int nsamples)
FIXME: eradicate Domain in favor of Binning.
Definition: Response.cxx:303
double interpolate(std::vector< double > &xData, std::vector< double > &yData, double x, bool extrapolate)
WIRECELL_FACTORY(EmpiricalNoiseModel, WireCell::Gen::EmpiricalNoiseModel, WireCell::IChannelSpectrum, WireCell::IConfigurable) using namespace WireCell
Monte Carlo Simulation.
logptr_t logger(std::string name)
Definition: Logging.cxx:71
void log(source_loc source, level::level_enum lvl, const char *fmt, const Args &...args)
Definition: spdlog.h:165
Definition: Main.h:22
void scale(Sequence< Val > &seq, Val scalar)
Scale (multiply) sequence values by scalar.
Definition: Waveform.h:146
static const double us
Definition: Units.h:101
realseq_t magnitude(const compseq_t &seq)
Return the magnitude or absolute value of the sequence.
Definition: Waveform.cxx:65
Json::Value Configuration
Definition: Configuration.h:50
array_xxc dft(const array_xxf &arr)
Definition: Array.cxx:15
def load(filename, jpath="depos")
Definition: depos.py:34
Sequence< Val > resample(const Sequence< Val > &wave, const Domain &domain, int nsamples, const Domain &newdomain)
Definition: Waveform.h:92