A Discrete-Event Network Simulator
API
two-ray-to-three-gpp-ch-calibration.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 """
4 Companion TwoRaySpectrumPropagationLossModel calibration script
5 
6 Copyright (c) 2022 SIGNET Lab, Department of Information Engineering,
7 University of Padova
8 
9 This program is free software: you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option) any later
12 version.
13 
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with
19 this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21 
22 import pandas as pd
23 from matplotlib import pyplot as plt
24 import seaborn as sns
25 import numpy as np
26 from pathlib import Path
27 from itertools import product
28 from tqdm import tqdm
29 import joblib
30 import contextlib
31 import argparse as argp
32 
33 
34 # Command line arguments
35 parser = argp.ArgumentParser(formatter_class=argp.ArgumentDefaultsHelpFormatter)
36 parser.add_argument("--num_search_grid_params", default=30,
37  help="Number of values for each parameter of the search grids")
38 parser.add_argument("--num_refinements", default=1,
39  help="Number of refinement local search runs to be carried out")
40 parser.add_argument("--ref_data_fname", default="two-ray-to-three-gpp-splm-calibration.csv",
41  help="Filename of the fit reference data, obtained from ns-3")
42 parser.add_argument("--fit_out_fname", default="two-ray-splm-fitted-params.txt",
43  help="Filename of the fit results")
44 parser.add_argument("--c_plus_plus_out_fname", default="two-ray-cplusplus-fitted-params.txt",
45  help="Filename of the fit results, encoded as a C++ data structure to be imported in ns-3")
46 parser.add_argument("--figs_folder", default="FiguresTwoRayThreeGppChCalibration/",
47  help="Output folder for the fit results figures")
48 parser.add_argument("--epsilon", default=1e-7,
49  help="Tolerance value for the preliminary tests")
50 parser.add_argument("--preliminary_fit_test", default=True,
51  help="Whether to run preliminary tests which check the correctness of the script functions")
52 parser.add_argument("--fit_ftr_to_threegpp", default=True,
53  help="Whether to run the calibration with respect to the 3GPP reference channel gains")
54 parser.add_argument("--output_ns3_table", default=True,
55  help="Whether to output the code for importing the calibration results in ns-3")
56 parser.add_argument("--plot_fit_results", default=False,
57  help="Whether to plot a comparison of the reference data ECDFs vs the fitted FTR distributions")
58 
59 args = parser.parse_args()
60 # Number of values for each parameter of the search grids
61 num_search_grid_params = int(args.num_search_grid_params)
62 # Number of refinement local search runs to be carried out
63 num_refinements = int(args.num_refinements)
64 # Filename of the fit reference data, obtained from ns-3
65 ref_data_fname = args.ref_data_fname
66 # Filename of the fit results
67 fit_out_fname = args.fit_out_fname
68 # Filename of the fit results, encoded as a C++ data structure to be imported in ns-3
69 c_plus_plus_out_fname = args.c_plus_plus_out_fname
70 # Output folder for the fit results figures
71 figs_folder = args.figs_folder
72 # Tolerance value for the preliminary tests
73 epsilon = float(args.epsilon)
74 # Whether to run preliminary tests which check the correctness of the script functions
75 preliminary_fit_test = bool(args.preliminary_fit_test)
76 # Whether to run the calibration with respect to the 3GPP reference channel gains
77 fit_ftr_to_threegpp = bool(args.fit_ftr_to_threegpp)
78 # Whether to output the code for importing the calibration results in ns-3
79 output_ns3_table = bool(args.output_ns3_table)
80 # Whether to plot a comparison of the reference data ECDFs vs the fitted FTR distributions
81 plot_fit_results = bool(args.plot_fit_results)
82 
83 
84 @contextlib.contextmanager
85 def tqdm_joblib(tqdm_object):
86  """
87  Context manager to patch joblib to report into tqdm progress bar given as argument.
88  Taken from: https://stackoverflow.com/questions/24983493/tracking-progress-of-joblib-parallel-execution
89 
90  """
91  class TqdmBatchCompletionCallback(joblib.parallel.BatchCompletionCallBack):
92  def __call__(self, *args, **kwargs):
93  tqdm_object.update(n=self.batch_size)
94  return super().__call__(*args, **kwargs)
95 
96  old_batch_callback = joblib.parallel.BatchCompletionCallBack
97  joblib.parallel.BatchCompletionCallBack = TqdmBatchCompletionCallback
98  try:
99  yield tqdm_object
100  finally:
101  joblib.parallel.BatchCompletionCallBack = old_batch_callback
102  tqdm_object.close()
103 
104 
105 
106 class FtrParams:
107 
115 
116  def __init__(self, m: float, sigma: float, k: float, delta: float):
117  '''! The initializer.
118  @param self: the object pointer
119  @param m: Parameter m for the Gamma variable. Used both as the shape and rate parameters.
120  @param sigma: Parameter sigma. Used as the variance of the amplitudes of the normal diffuse components.
121  @param k: Parameter K. Expresses ratio between dominant specular components and diffuse components.
122  @param delta: Parameter delta [0, 1]. Expresses how similar the amplitudes of the two dominant specular components are.
123  '''
124 
125  self.mm = m
126  self.sigmasigma = sigma
127  self.kk = k
128  self.deltadelta = delta
129 
130  def __init__(self):
131  '''! The initializer with default values.
132  @param self: the object pointer
133  '''
134 
135  self.mm = 1
136  self.sigmasigma = 1.0
137  self.kk = 0.0
138  self.deltadelta = 0.0
139 
140  def __str__(self):
141  '''! The initializer with default values.
142  @param self: the object pointer
143  @returns A string reporting the value of each of the FTR fading model parameters
144  '''
145 
146  return f'm: {self.m}, sigma: {self.sigma}, k: {self.k}, delta: {self.delta}'
147 
148 
149 def get_ftr_ecdf(params: FtrParams, n_samples: int, db=False):
150  '''! Returns the ECDF for the FTR fading model, for a given parameter grid.
151  @param params: The FTR parameters grid.
152  @param n_samples: The number of samples of the output ECDF
153  @param db: Whether to return the ECDF with the gain expressed in dB
154  @returns The ECDF for the FTR fading model
155  '''
156 
157  assert (params.delta >= 0 and params.delta <= 1.0)
158 
159  # Compute the specular components amplitudes from the FTR parameters
160  cmn_sqrt_term = np.sqrt(1 - params.delta**2)
161  v1 = np.sqrt(params.sigma) * np.sqrt(params.k * (1 - cmn_sqrt_term))
162  v2 = np.sqrt(params.sigma) * np.sqrt(params.k * (1 + cmn_sqrt_term))
163 
164  assert (abs((v1**2 + v2**2)/(2*params.sigma) - params.k) < 1e-5)
165  if params.k > 0:
166  assert (abs((2*v1*v2)/(v1**2 + v2**2) - params.delta) < 1e-4)
167  else:
168  assert (v1 == v2 == params.k)
169 
170  sqrt_gamma = np.sqrt(np.random.gamma(
171  shape=params.m, scale=1/params.m, size=n_samples))
172 
173  # Sample the random phases of the specular components, which are uniformly distributed in [0, 2*PI]
174  phi1 = np.random.uniform(low=0, high=1.0, size=n_samples)
175  phi2 = np.random.uniform(low=0, high=1.0, size=n_samples)
176 
177  # Sample the normal-distributed real and imaginary parts of the diffuse components
178  x = np.random.normal(scale=np.sqrt(params.sigma), size=n_samples)
179  y = np.random.normal(scale=np.sqrt(params.sigma), size=n_samples)
180 
181  # Compute the channel response by combining the above terms
182  compl_phi1 = np.vectorize(complex)(np.cos(phi1), np.sin(phi1))
183  compl_phi2 = np.vectorize(complex)(np.cos(phi2), np.sin(phi2))
184  compl_xy = np.vectorize(complex)(x, y)
185  h = np.multiply(sqrt_gamma, compl_phi1) * v1 + \
186  np.multiply(sqrt_gamma, compl_phi2) * v2 + compl_xy
187 
188  # Compute the squared norms
189  power = np.square(np.absolute(h))
190 
191  if db:
192  power = 10*np.log10(power)
193 
194  return np.sort(power)
195 
196 
197 def compute_ftr_mean(params: FtrParams):
198  '''! Computes the mean of the FTR fading model, given a specific set of parameters.
199  @param params: The FTR fading model parameters.
200  '''
201 
202  cmn_sqrt_term = np.sqrt(1 - params.delta**2)
203  v1 = np.sqrt(params.sigma) * np.sqrt(params.k * (1 - cmn_sqrt_term))
204  v2 = np.sqrt(params.sigma) * np.sqrt(params.k * (1 + cmn_sqrt_term))
205 
206  mean = v1**2 + v2**2 + 2*params.sigma
207 
208  return mean
209 
210 
211 def compute_ftr_th_mean(params: FtrParams):
212  '''! Computes the mean of the FTR fading model using the formula reported in the corresponding paper,
213  given a specific set of parameters.
214  @param params: The FTR fading model parameters.
215  '''
216 
217  return 2 * params.sigma * (1 + params.k)
218 
219 
220 def compute_anderson_darling_measure(ref_ecdf: list, target_ecdf: list) -> float:
221  '''! Computes the Anderson-Darling measure for the specified reference and targets distributions.
222  In particular, the Anderson-Darling measure is defined as:
223  A^2 = -N -S, where S = \sum_{i=1}^N \frac{2i - 1}{N} \left[ ln F(Y_i) + ln F(Y_{N + 1 - i}) \right].
224 
225  See https://www.itl.nist.gov/div898/handbook/eda/section3/eda35e.htm for further details.
226 
227  @param ref_ecdf: The reference ECDF.
228  @param target_ecdf: The target ECDF we wish to match the reference distribution to.
229  @returns The Anderson-Darling measure for the specified reference and targets distributions.
230  '''
231 
232  assert (len(ref_ecdf) == len(target_ecdf))
233 
234  n = len(ref_ecdf)
235  mult_factors = np.linspace(start=1, stop=n, num=n)*2 + 1
236  ecdf_values = compute_ecdf_value(ref_ecdf, target_ecdf)
237 
238  # First and last elements of the ECDF may lead to NaNs
239  with np.errstate(divide='ignore'):
240  log_a_plus_b = np.log(ecdf_values) + np.log(1 - np.flip(ecdf_values))
241 
242  valid_idxs = np.isfinite(log_a_plus_b)
243  A_sq = - np.dot(mult_factors[valid_idxs], log_a_plus_b[valid_idxs])
244 
245  return A_sq
246 
247 
248 def compute_ecdf_value(ecdf: list, data_points: float) -> np.ndarray:
249  '''! Given an ECDF and data points belonging to its domain, returns their associated EDCF value.
250  @param ecdf: The ECDF, represented as a sorted list of samples.
251  @param data_point: A list of data points belonging to the same domain as the samples.
252  @returns The ECDF value of the domain points of the specified ECDF
253  '''
254 
255  ecdf_values = []
256  for point in data_points:
257  idx = np.searchsorted(ecdf, point) / len(ecdf)
258  ecdf_values.append(idx)
259 
260  return np.asarray(ecdf_values)
261 
262 
263 def get_sigma_from_k(k: float) -> float:
264  '''! Computes the value for the FTR parameter sigma, given k, yielding a unit-mean fading process.
265  @param k: The K parameter of the FTR fading model, which represents the ratio of the average power
266  of the dominant components to the power of the remaining diffuse multipath.
267  @returns The value for the FTR parameter sigma, given k, yielding a unit-mean fading process.
268  '''
269 
270  return 1 / (2 + 2 * k)
271 
272 
273 def fit_ftr_to_reference(ref_data: pd.DataFrame, ref_params_combo: tuple, num_params: int, num_refinements: int) -> str:
274  '''! Estimate the FTR parameters yielding the closest ECDF to the reference one.
275 
276  Uses a global search to estimate the FTR parameters yielding the best fit to the reference ECDF.
277  Then, the search is refined by repeating the procedure in the neighborhood of the parameters
278  identified with the global search. Such a neighborhood is determined as the interval whose center
279  is the previous iteration best value, and the lower and upper bounds are the first lower and upper
280  values which were previously considered, respectively.
281 
282  @param ref_data: The reference data, represented as a DataFrame of samples.
283  @param ref_params_combo: The specific combination of simulation parameters corresponding
284  to the reference ECDF
285  @param num_params: The number of values of each parameter in the global and local search grids.
286  @param num_refinements: The number of local refinement search to be carried out after the global search.
287 
288  @returns An estimate of the FTR parameters yielding the closest ECDF to the reference one.
289  '''
290 
291  # Retrieve the reference ECDF
292  ref_ecdf = ref_data.query(
293  'scen == @ref_params_combo[0] and cond == @ref_params_combo[1] and fc == @ref_params_combo[2]')
294 
295  # Perform the fit
296  n_samples = len(ref_ecdf)
297  best_params = FtrParams()
298  best_ad = np.inf
299 
300  # The m and K parameters can range in ]0, +inf[
301  m_and_k_ub = 4
302  m_and_k_lb = 0.001
303  m_and_k_step = (m_and_k_ub - m_and_k_lb) / n_samples
304 
305  # The delta parameter can range in [0, 1]
306  delta_step = 1/n_samples
307 
308  # Define the coarse grid
309  coarse_search_grid = {
310  # m must be in [0, +inf]
311  'm': np.power(np.ones(num_params)*10, np.linspace(start=m_and_k_lb, stop=m_and_k_ub, endpoint=True, num=num_params)),
312  # k must be in [0, +inf]
313  'k': np.power(np.ones(num_params)*10, np.linspace(start=m_and_k_lb, stop=m_and_k_ub, endpoint=True, num=num_params)),
314  # delta must be in [0, 1]
315  'delta': np.linspace(start=0.0, stop=1.0, endpoint=True, num=num_params)
316  # sigma determined from k, due to the unit-mean constraint
317  }
318 
319  for element in product(*coarse_search_grid.values()):
320 
321  # Create FTR params object
322  params = FtrParams()
323  params.m = element[0]
324  params.k = element[1]
325  params.delta = element[2]
326  params.sigma = get_sigma_from_k(params.k)
327 
328  # Retrieve the corresponding FTR ECDF
329  ftr_ecdf = get_ftr_ecdf(params, n_samples, db=True)
330  ad_meas = compute_anderson_darling_measure(ref_ecdf, ftr_ecdf)
331 
332  if (ad_meas < best_ad):
333  best_params = params
334  best_ad = ad_meas
335 
336  for _ in range(num_refinements):
337  # Refine search in the neighborhood of the previously identified params
338  finer_search_grid = {
339  'm': np.power(np.ones(num_params)*10,
340  np.linspace(start=max(0, np.log10(best_params.m) - m_and_k_step),
341  stop=np.log10(best_params.m) +
342  m_and_k_step,
343  endpoint=True, num=num_params)),
344  'k': np.power(np.ones(num_params)*10,
345  np.linspace(start=max(0, np.log10(best_params.k) - m_and_k_step),
346  stop=np.log10(best_params.k) +
347  m_and_k_step,
348  endpoint=True, num=num_params)),
349  'delta': np.linspace(start=max(0, best_params.delta - delta_step),
350  stop=min(1, best_params.delta + delta_step),
351  endpoint=True, num=num_params)
352  # sigma determined from k, due to the unit-mean constraint
353  }
354 
355  m_and_k_step = (np.log10(best_params.m) + m_and_k_step -
356  max(0, np.log10(best_params.m) - m_and_k_step)) / n_samples
357  delta_step = (min(1, best_params.delta + 1/num_params) -
358  max(0, best_params.delta - 1/num_params)) / n_samples
359 
360  for element in product(*finer_search_grid.values()):
361 
362  # Create FTR params object
363  params = FtrParams()
364  params.m = element[0]
365  params.k = element[1]
366  params.delta = element[2]
367  params.sigma = get_sigma_from_k(params.k)
368 
369  # Retrieve the corresponding FTR ECDF
370  ftr_ecdf = get_ftr_ecdf(params, n_samples, db=True)
371  ad_meas = compute_anderson_darling_measure(ref_ecdf, ftr_ecdf)
372 
373  if (ad_meas < best_ad):
374  best_params = params
375  best_ad = ad_meas
376 
377  out_str = f"{ref_params_combo[0]}\t{ref_params_combo[1]}\t{ref_params_combo[2]}" + \
378  f" \t{best_params.sigma}\t{best_params.k}\t{best_params.delta}\t{best_params.m}\n"
379 
380  return out_str
381 
382 
383 def append_ftr_params_to_cpp_string(text: str, params: FtrParams) -> str:
384  text += f'TwoRaySpectrumPropagationLossModel::FtrParams({np.format_float_scientific(params.m)}, {np.format_float_scientific(params.sigma)}, \
385  {np.format_float_scientific(params.k)}, {np.format_float_scientific(params.delta)})'
386 
387  return text
388 
389 
390 def print_cplusplus_map_from_fit_results(fit: pd.DataFrame, out_fname: str):
391  """
392  Prints to a file the results of the FTR fit, as C++ code.
393 
394  Args:
395  fit (pd.DataFrame): A Pandas Dataframe holding the results of the FTR fit.
396  out_fname (str): The name of the file to print the C++ code to.
397  """
398 
399  out_str = '{'
400 
401  for scen in set(fit['scen']):
402  out_str += f'{{\"{scen}\",\n{{'
403 
404  for cond in set(fit['cond']):
405  out_str += f'{{ChannelCondition::LosConditionValue::{cond}, \n'
406 
407  # Print vector of carrier frequencies
408  freqs = np.sort(list(set(fit['fc'])))
409  out_str += "{{"
410  for fc in freqs:
411  out_str += f'{float(fc)}, '
412  out_str = out_str[0:-2]
413  out_str += '},\n{'
414 
415  # Load corresponding fit results
416  for fc in freqs:
417  fit_line = fit.query(
418  'scen == @scen and cond == @cond and fc == @fc')
419  assert(fit_line.reset_index().shape[0] == 1)
420 
421  params = FtrParams()
422  params.m = fit_line.iloc[0]['m']
423  params.k = fit_line.iloc[0]['k']
424  params.delta = fit_line.iloc[0]['delta']
425  params.sigma = fit_line.iloc[0]['sigma']
426 
427  # Print vector of corresponding FTR parameters
428  out_str = append_ftr_params_to_cpp_string(out_str, params)
429  out_str += ', '
430 
431  out_str = out_str[0:-2]
432  out_str += '}'
433  out_str += '}},\n'
434 
435  out_str = out_str[0:-2]
436  out_str += '}},\n'
437 
438  out_str = out_str[0:-2]
439  out_str += '}\n'
440 
441  f = open(out_fname, "w")
442  f.write(out_str)
443  f.close()
444 
445 
446 if __name__ == '__main__':
447 
448 
451 
452  # Load reference data obtained from the ns-3 TR 38.901 implementation
453  df = pd.read_csv(ref_data_fname, sep='\t')
454  # Linear gain --> gain in dB
455  df['gain'] = 10*np.log10(df['gain'])
456 
457  # Retrieve the possible parameters configurations
458  scenarios = set(df['scen'])
459  is_los = set(df['cond'])
460  frequencies = np.sort(list(set(df['fc'])))
461 
462 
465 
466  if preliminary_fit_test:
467 
468  params = FtrParams()
469  get_ftr_ecdf(params, 100)
470 
471  # Make sure the mean is indeed independent from the delta parameter
472  mean_list = []
473  params = FtrParams()
474  for delta in np.linspace(start=0, stop=1, num=100):
475  params.delta = delta
476  mean_list.append(compute_ftr_mean(params))
477 
478  avg_mean = np.mean(mean_list)
479  assert np.all(np.abs(mean_list - avg_mean) < epsilon)
480 
481  # Make sure that we are indeed generating parameters yielding unit-mean
482  mean_list.clear()
483  mean_th_list = []
484  params = FtrParams()
485  for k in np.linspace(start=1, stop=500, num=50):
486  sigma = get_sigma_from_k(k)
487  params.sigma = sigma
488  params.k = k
489  mean_list.append(compute_ftr_mean(params))
490  mean_th_list.append(compute_ftr_th_mean(params))
491 
492  assert np.all(np.abs(mean_list - np.float64(1.0)) < epsilon)
493  assert np.all(np.abs(mean_th_list - np.float64(1.0)) < epsilon)
494 
495  if fit_ftr_to_threegpp:
496 
497  # Parallel search for the different simulation parameters combination
498  with tqdm_joblib(tqdm(desc="Fitting FTR to the 3GPP fading model", total=(len(scenarios) * len(is_los) * len(frequencies)))) as progress_bar:
499  res = joblib.Parallel(n_jobs=10)(
500  joblib.delayed(fit_ftr_to_reference)(df, params_comb, num_search_grid_params, num_refinements) for params_comb in product(scenarios, is_los, frequencies))
501 
502  f = open(fit_out_fname, "w")
503  f.write("scen\tcond\tfc\tsigma\tk\tdelta\tm\n")
504  for line in res:
505  f.write(line)
506 
507  f.close()
508 
509  if output_ns3_table:
510 
511  # Load the fit results
512  fit = pd.read_csv(fit_out_fname, delimiter='\t')
513 
514  # Output the C++ data structure
515  print_cplusplus_map_from_fit_results(fit, c_plus_plus_out_fname)
516 
517  if plot_fit_results:
518 
519  # Set Seaborn defaults and setup output folder
520  sns.set(rc={'figure.figsize': (7, 5)})
521  sns.set_theme()
522  sns.set_style('darkgrid')
523 
524  fit = pd.read_csv(fit_out_fname, delimiter='\t')
525  # Create folder if it does not exist
526  Path(figs_folder).mkdir(parents=True, exist_ok=True)
527  ad_measures = []
528 
529  for params_comb in product(scenarios, is_los, frequencies):
530 
531  data_query = 'scen == @params_comb[0] and cond == @params_comb[1] and fc == @params_comb[2]'
532 
533  # Load corresponding reference data
534  ref_data = df.query(data_query)
535 
536  # Create FTR params object
537  fit_line = fit.query(data_query)
538  assert(fit_line.reset_index().shape[0] == 1)
539  params = FtrParams()
540  params.m = fit_line.iloc[0]['m']
541  params.k = fit_line.iloc[0]['k']
542  params.delta = fit_line.iloc[0]['delta']
543  params.sigma = fit_line.iloc[0]['sigma']
544 
545  # Retrieve the corresponding FTR ECDF
546  ftr_ecdf = get_ftr_ecdf(params, len(ref_data), db=True)
547 
548  # Compute the AD measure
550  np.sort(ref_data['gain']), ftr_ecdf)
551  ad_measures.append(np.sqrt(ad_meas))
552 
553  sns.ecdfplot(data=ref_data, x='gain',
554  label='38.901 reference model')
555  sns.ecdfplot(
556  ftr_ecdf, label=f'Fitted FTR, sqrt(AD)={round(np.sqrt(ad_meas), 2)}')
557  plt.xlabel(
558  'End-to-end channel gain due to small scale fading [dB]')
559  plt.legend()
560  plt.savefig(
561  f'{figs_folder}{params_comb[0]}_{params_comb[1]}_{params_comb[2]/1e9}GHz_fit.png', dpi=500, bbox_inches='tight')
562  plt.clf()
563 
564  # Plot ECDF of the scaled and normalized AD measures
565  sns.ecdfplot(ad_measures, label='AD measures')
566  plt.xlabel('Anderson-Darling goodness-of-fit')
567  plt.legend()
568  plt.savefig(f'{figs_folder}AD_measures.png',
569  dpi=500, bbox_inches='tight')
570  plt.clf()
#define min(a, b)
Definition: 80211b.c:42
#define max(a, b)
Definition: 80211b.c:43
def __str__(self)
The initializer with default values.
def __init__(self, float m, float sigma, float k, float delta)
The initializer.
str append_ftr_params_to_cpp_string(str text, FtrParams params)
float compute_anderson_darling_measure(list ref_ecdf, list target_ecdf)
Computes the Anderson-Darling measure for the specified reference and targets distributions.
def compute_ftr_th_mean(FtrParams params)
Computes the mean of the FTR fading model using the formula reported in the corresponding paper,...
str fit_ftr_to_reference(pd.DataFrame ref_data, tuple ref_params_combo, int num_params, int num_refinements)
Estimate the FTR parameters yielding the closest ECDF to the reference one.
def get_ftr_ecdf(FtrParams params, int n_samples, db=False)
Returns the ECDF for the FTR fading model, for a given parameter grid.
def print_cplusplus_map_from_fit_results(pd.DataFrame fit, str out_fname)
float get_sigma_from_k(float k)
Computes the value for the FTR parameter sigma, given k, yielding a unit-mean fading process.
def compute_ftr_mean(FtrParams params)
Computes the mean of the FTR fading model, given a specific set of parameters.
np.ndarray compute_ecdf_value(list ecdf, float data_points)
Given an ECDF and data points belonging to its domain, returns their associated EDCF value.
#define list