Belle II Software  release-08-01-10
SVDSimBase.py
1 #!/usr/bin/env python
2 # coding: utf8
3 
4 
11 
12 """
13 SVDSimBase.py
14 ==========
15 The module contains basic functions for generation and analysis of SVD strip data.
16 
17 Description:
18 This is a set of utility functions and constant declarations for simulation of SVD strip data
19 to use in training timing networks or for other purposes. These include wave functions (gamma,
20 cubic polynomial and beta-prime).
21 
22 Usage:
23 Just import the module to use it.
24 """
25 
26 import numpy as np
27 import pandas as pd
28 from scipy.stats import norm, uniform
29 
30 dt = 31.44 # Time interval between APV25 samples
31 threshold_cut = 3 # Samples with signal-to-noise less than 3 are zero.
32 
33 wexp_default_tau = 55 # ns
34 
35 
36 def wexp(t, tau=wexp_default_tau):
37  '''
38  Gamma waveform
39  wexp(t, tau) = t/tau * exp(1-t/tau) if t > 0 else 0,
40  normalized to peak value 1.
41  t - nummpy vector of times
42  tau - (scalar) waveform decay time
43  return: numpy vector of wexp(t, tau) values at times t
44  '''
45  z = t / tau
46  return np.clip(z * np.exp(1.0 - z), 0.0, 100.0)
47 
48 
49 w3_default_tau = 200 # ns
50 
51 
52 def w3(t, tau=w3_default_tau):
53  ''' Cubic waveform
54  w3(t, tau) = 27/4 * t/tau * (1-t/tau)**2 if 0 < t < tau else 0,
55  normalized to peak value 1.
56  t - numpy vector of times
57  tau - (scalar) width of the distribution
58  return - numpy vector of w3(t,tau) values at times t
59  '''
60  z = np.clip(t / tau, 0, 1)
61  return 27 / 4 * z * (z - 1) * (z - 1)
62 
63 
64 bp_default_tau = 250 # ns
65 
66 
67 def betaprime_wave(t, tau=bp_default_tau):
68  ''' Beta-prime waveform
69  betaprime_wave(t, tau) = 149.012 * (t/tau)**2 * (1 + t/tau)**10 if t > 0 else 0
70  t - numpy vector of times
71  tau - (scalar) width of the distribution
72  return - numpy vector of betaprime_wave values of betaprime_wave at times t
73  '''
74  z = np.clip(t / tau, 0, 1000)
75  return 149.012 * z**2 / (1 + z)**10
76 
77 
78 def test3(s, threshold):
79  '''Test for 3 consecutive non-zero APV samples
80  A six-tuple of APV25 samples is only accepted if it has 3 consecutive samples over threshold.
81  s - 6 apv samples
82  return - True if there are 3 consecutive samples over threshold, otherwise False.
83  '''
84  cons_before = np.zeros(len(s))
85  for i in range(3):
86  cons_before[i:] += (s > threshold)[0:6 - i]
87  return np.max(cons_before) >= 3
88 
89 
90 '''
91 This is the list of all configurations of over-the-threhsold samples that pass the 3-over-the-threshold-samples test.
92 '''
93 residual_configs = [
94  [3, 4, 5],
95  [2, 3, 4],
96  [2, 3, 4, 5],
97  [1, 3, 4, 5],
98  [1, 2, 3],
99  [1, 2, 3, 5],
100  [1, 2, 3, 4],
101  [1, 2, 3, 4, 5],
102  [0, 3, 4, 5],
103  [0, 2, 3, 4],
104  [0, 2, 3, 4, 5],
105  [0, 1, 3, 4, 5],
106  [0, 1, 2],
107  [0, 1, 2, 5],
108  [0, 1, 2, 4],
109  [0, 1, 2, 4, 5],
110  [0, 1, 2, 3],
111  [0, 1, 2, 3, 5],
112  [0, 1, 2, 3, 4],
113  [0, 1, 2, 3, 4, 5]
114 ]
115 
116 
117 def gen_signal(ampl, t0, tau, sigma=1, w=betaprime_wave, tau_sigma=0.0):
118  '''Generate random sample of 6 APV signals, properly censored.
119  The produced data are normalized to sigma = 1 and the given amplitude.
120  The procedure is:
121  - real_amplitude = sigma * amlitude
122  - generate waveform, add noise, and convert to integer values
123  - divide signals by sigma
124  For large amplitudes, the function adds gaussian noise to generated waveform.
125  For amplitudes around and below the threshold, the function randomly selects a
126  subset of samples over threshold and generates over-threhold noises from left-censored
127  gaussian distributions.
128  ampl - amplitude
129  t0 - time shift
130  tau - decay time or width of the waveform
131  sigma - nosie (default 1: we work with S/N rather than raw signals)
132  tau_sigma - width of tau jitter
133  '''
134  # reconstruct amplitude
135  gen_amplitude = sigma * ampl
136  # threshold for data generration from cdf
137  gen_thr = int(sigma * threshold_cut + 1.0 - 1.0e-9) - 0.5
138  res = np.zeros(6)
139  # Add tau jitter, if desired.
140  if tau_sigma > 0:
141  tau += np.random.randn() * tau_sigma
142  res0 = gen_amplitude * w(np.linspace(-dt - t0, 4 * dt - t0, 6, endpoint=True), tau)
143  if test3(res0, threshold_cut * sigma):
144  res = (res0 + sigma * np.random.randn(6) + 0.5).astype(int)
145  # We just repeat if the configuration doesn't pass (should happen rarely)
146  while not test3(res, threshold_cut * sigma):
147  res = (res0 + sigma * np.random.randn(6) + 0.5).astype(int)
148  res[res < threshold_cut * sigma] = 0
149  else: # low-amp mode
150  # calculate probabilities of saamples above threhold
151  p_over = 1.0 - norm.cdf((gen_thr - res0) / sigma)
152  # calculate probabilities of over-threhold configurations
153  pconfs = np.array([np.prod(p_over[conf]) for conf in residual_configs])
154  pconfs /= np.sum(pconfs)
155  cconfs = np.cumsum(pconfs)
156  # select random configuration
157  u_conf = uniform.rvs()
158  i_conf = 0 # meaning configuration 0
159  while u_conf > cconfs[i_conf]:
160  i_conf += 1
161  u_res = uniform.rvs(0, 1, len(residual_configs[i_conf]))
162  res = np.zeros(6)
163  res[residual_configs[i_conf]] = res0[residual_configs[i_conf]] + \
164  sigma * norm.ppf(1 - u_res * p_over[residual_configs[i_conf]])
165  res = (res + 0.5).astype(int)
166  return res / sigma
167 
168 # ==============================================================================
169 
170 
172  # ------------------------------------------------------------------------------
173  '''
174  A simple class to encode and decode tau values for network training
175  based on amplitude and tau ranges.
176  '''
177 
178  def __init__(self, amp_range, tau_range):
179  """
180  creates the class
181  """
182 
183  self.amp_min, self.amp_maxamp_max = amp_range
184 
185  self.tau_min, self.tau_maxtau_max = tau_range
186 
187  self.at_ratioat_ratio = (self.amp_maxamp_max - self.amp_min) / (self.tau_maxtau_max - self.tau_min)
188 
189  def encode(self, tau):
190  """
191  encoder of tau values for network training
192  """
193  return (self.amp_min + self.at_ratioat_ratio * (tau - self.tau_min))
194 
195  def decode(self, etau):
196  """
197  decoder of tau values for network training
198  """
199  return (self.tau_min + 1.0 / self.at_ratioat_ratio * (etau - self.amp_min))
200 
201 
203  """
204  This class generates a Pandas dataframe with a random sample of SVD strip signals with specified size and parameters.
205  NB:
206  1. We generate time bins from quantiles, do we want a regular grid?
207  2. Have to think of possible irregular grid.
208  """
209 
210  def __init__(self, t0_bounds, tau_bounds, amplitude_bounds, sigma_bounds, tau_sigma, bin_size, wf=betaprime_wave):
211  """
212  The constructor takes the following parameters:
213  t0_bounds is a tuple, (t0_min, t0_max)
214  tau_bounds is a tuple (tau_min, tau_max)
215  amplitude_bounds is a tuple (amp_min, amp_max)
216  sigma_bounds is a tuple (sigma_min, sigma_max)
217  bin_size is the % fraction of t0_min, t0_max interval corresponding to a single output t0 bin.
218  """
219 
220 
221  self.t0_min, self.t0_maxt0_max = t0_bounds
222 
223  self.tau_mintau_min, self.tau_maxtau_max = tau_bounds
224 
225  self.amp_min, self.amp_maxamp_max = amplitude_bounds
226 
227  self.sigma_min, self.sigma_maxsigma_max = sigma_bounds
228 
229  self.tau_codertau_coder = tau_encoder(amplitude_bounds, tau_bounds)
230 
231  self.tau_sigmatau_sigma = tau_sigma
232 
233  self.bin_sizebin_size = bin_size
234 
235  self.wfwf = wf
236 
237  def get_t0_bounds(self):
238  '''
239  Get t0 bounds of sampling space
240  '''
241  return (self.t0_min, self.t0_maxt0_max)
242 
243  def get_amp_bounds(self):
244  '''
245  Get amplitude bounds of sampling space
246  '''
247  return (self.amp_min, self.amp_maxamp_max)
248 
249  def get_tau_bounds(self):
250  '''
251  Get tau bounds of sampling space
252  '''
253  return (self.tau_mintau_min, self.tau_maxtau_max)
254 
255  def set_tau_bounds(self, tau_min, tau_max):
256  '''
257  Set width limits for the simulation.
258  '''
259 
260  self.tau_mintau_min = tau_min
261  self.tau_maxtau_max = tau_max
262 
263  def set_tau_sigma(self, tau_sigma):
264  '''
265  Set width jitter for the simulation.
266  '''
267  self.tau_sigmatau_sigma = tau_sigma
268 
269  def get_tau_sigma(self):
270  '''
271  Get width jitter for the simulation.
272  '''
273  return self.tau_sigmatau_sigma
274 
275  def get_sigma_bounds(self):
276  '''
277  Get sigma bounds
278  '''
279  return (self.sigma_min, self.sigma_maxsigma_max)
280 
281  def generate(self, sample_size):
282  '''
283  Generate sample_size samples.
284  '''
285 
286  self.n_samplesn_samples = sample_size
287 
288  self.stockdatastockdata = pd.DataFrame({
289  'test': np.random.uniform(size=self.n_samplesn_samples),
290  't0': np.random.uniform(self.t0_min, self.t0_maxt0_max, size=self.n_samplesn_samples),
291  'tau': np.random.uniform(self.tau_mintau_min, self.tau_maxtau_max, size=self.n_samplesn_samples),
292  'sigma': np.random.uniform(self.sigma_min, self.sigma_maxsigma_max, size=self.n_samplesn_samples),
293  'amplitude': np.random.uniform(self.amp_min, self.amp_maxamp_max, size=self.n_samplesn_samples),
294  's1': np.zeros(self.n_samplesn_samples),
295  's2': np.zeros(self.n_samplesn_samples),
296  's3': np.zeros(self.n_samplesn_samples),
297  's4': np.zeros(self.n_samplesn_samples),
298  's5': np.zeros(self.n_samplesn_samples),
299  's6': np.zeros(self.n_samplesn_samples)
300  })
301  self.stockdatastockdata['normed_tau'] = self.stockdatastockdata.apply(lambda row: self.tau_codertau_coder.encode(row.tau), axis=1)
302  orderedcols = ['test', 'amplitude', 't0', 'tau', 'sigma', 's1', 's2', 's3', 's4', 's5', 's6', 'normed_tau']
303  self.stockdatastockdata = self.stockdatastockdata[orderedcols]
304  # This is where the data are generated
305  self.stockdatastockdata[['s' + str(i) for i in range(1, 7)]] = self.stockdatastockdata.apply(lambda row: pd.Series(
306  gen_signal(row.amplitude, row.t0, row.tau, row.sigma, tau_sigma=self.tau_sigmatau_sigma, w=self.wfwf)), axis=1)
307 
308  self.t0_binst0_bins = np.percentile(self.stockdatastockdata.t0, np.arange(0, 101, self.bin_sizebin_size))
309  self.t0_binst0_bins[0] = self.t0_binst0_bins[0] - 0.1
310  self.t0_binst0_bins[-1] = self.t0_binst0_bins[-1] + 0.1
311  self.stockdatastockdata['t0_bin'] = np.digitize(self.stockdatastockdata.t0, self.t0_binst0_bins)
312 
313  self.t0_bin_timest0_bin_times = self.stockdatastockdata['t0'].groupby(self.stockdatastockdata.t0_bin).aggregate(np.mean)
314  abins = np.arange(self.amp_min, self.amp_maxamp_max + 1, 1)
315  self.stockdatastockdata['abin'] = np.digitize(self.stockdatastockdata.amplitude, abins)
316  return self.stockdatastockdata
317 
318  def get_t0_array(self):
319  '''
320  Get array of mean t0's for classifier bins
321  '''
322  return self.t0_bin_timest0_bin_times
323 
324  def get_t0_bins(self):
325  '''
326  Get array of mean t0's for classifier bins
327  '''
328  return pd.Series(self.t0_binst0_bins)
329 
330 
331 # ==============================================================================
332 # Tau (scale) conversion and encoding
333 # ------------------------------------------------------------------------------
334 '''
335 Empirical ranges of raw waveform width values from February 2017 testbeam data.
336 These values are only used for scaling and not critical.
337 '''
338 raw_tau_min = 27
339 raw_tau_max = 45
340 
341 
342 def tau_hao2real(hao_tau):
343  '''
344  Convert Hao's raw tau (integral, in latency units) to correct betaprime scale. Includes scaling and fit adjustment.
345  '''
346  return 3.93 / 0.50305 * hao_tau
347 
348 
349 # ------------------------------------------------------------------------------
350 
351 if __name__ == '__main__':
352  print('Basic functions for simulation of SVD strip data.\nImport to use.')
def set_tau_sigma(self, tau_sigma)
Definition: SVDSimBase.py:263
def set_tau_bounds(self, tau_min, tau_max)
Definition: SVDSimBase.py:255
def __init__(self, t0_bounds, tau_bounds, amplitude_bounds, sigma_bounds, tau_sigma, bin_size, wf=betaprime_wave)
Definition: SVDSimBase.py:210
t0_bin_times
undocumented variable
Definition: SVDSimBase.py:313
t0_bins
undocumented variable
Definition: SVDSimBase.py:308
def generate(self, sample_size)
Definition: SVDSimBase.py:281
def decode(self, etau)
Definition: SVDSimBase.py:195
def __init__(self, amp_range, tau_range)
Definition: SVDSimBase.py:178