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