• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aewallin / allantools / 6997134815

26 Nov 2023 05:57PM UTC coverage: 74.663% (-10.1%) from 84.742%
6997134815

push

github

aewallin
coverage workflow

1273 of 1705 relevant lines covered (74.66%)

2.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.13
/allantools/ci.py
1
"""
2

3
this file is part of allantools, https://github.com/aewallin/allantools
4

5
- functions for confidence intervals
6
- functions for noise identification
7
- functions for computing equivalent degrees of freedom
8

9

10
License
11
-------
12

13
This program is free software: you can redistribute it and/or modify
14
it under the terms of the GNU Lesser General Public License as published by
15
the Free Software Foundation, either version 3 of the License, or
16
(at your option) any later version.
17

18
This program is distributed in the hope that it will be useful,
19
but WITHOUT ANY WARRANTY; without even the implied warranty of
20
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
GNU Lesser General Public License for more details.
22

23
You should have received a copy of the GNU Lesser General Public License
24
along with this program.  If not, see <http://www.gnu.org/licenses/>.
25

26
"""
27

28
import numpy as np
2✔
29
import scipy.stats  # used in confidence_intervals()
2✔
30
import scipy.signal  # decimation in lag-1 acf
2✔
31
import allantools as at
2✔
32

33
########################################################################
34
# Confidence Intervals
35
ONE_SIGMA_CI = scipy.special.erf(1/np.sqrt(2))
2✔
36
#    = 0.68268949213708585
37

38

39
def confidence_interval(dev, edf, ci=ONE_SIGMA_CI):
2✔
40
    """ returns confidence interval (dev_min, dev_max)
41
        for a given deviation dev, equivalent degrees of freedom edf,
42
        and degree of confidence ci.
43

44
    Parameters
45
    ----------
46
    dev: float
47
        Mean value (e.g. adev) around which we produce the confidence interval
48
    edf: float
49
        Equivalent degrees of freedon
50
    ci: float, defaults to scipy.special.erf(1/math.sqrt(2))
51
        for 1-sigma standard error set
52
        ci = scipy.special.erf(1/math.sqrt(2))
53
            = 0.68268949213708585
54

55
    Returns
56
    -------
57
    (dev_min, dev_max): (float, float)
58
        Confidence interval
59
    """
60
    ci_l = min(np.abs(ci), np.abs((ci-1))) / 2
3✔
61
    ci_h = 1 - ci_l
3✔
62

63
    # function from scipy, works OK, but scipy is large and slow to build
64
    chi2_l = scipy.stats.chi2.ppf(ci_l, edf)
3✔
65
    chi2_h = scipy.stats.chi2.ppf(ci_h, edf)
3✔
66

67
    variance = dev*dev
3✔
68
    var_l = float(edf) * variance / chi2_h  # NIST SP1065 eqn (45)
3✔
69
    var_h = float(edf) * variance / chi2_l
3✔
70
    return (np.sqrt(var_l), np.sqrt(var_h))
3✔
71

72

73
def confidence_interval_noiseID(x, dev, af, dev_type="adev",
3✔
74
                                data_type="phase", ci=ONE_SIGMA_CI):
3✔
75
    """ returns confidence interval (dev_min, dev_max)
×
76
        for a given deviation dev = Xdev( x, tau = af*(1/rate) )
×
77

78
        steps:
×
79
        1) identify noise type
×
80
        2) compute EDF
×
81
        3) compute confidence interval
×
82

83
    Parameters
×
84
    ----------
×
85
    x: numpy.array
×
86
        time-series
×
87
    dev: float
×
88
        Mean value (e.g. adev) around which we produce the confidence interval
×
89
    af: int
×
90
        averaging factor
×
91
    dev_type: string
×
92
        adev, oadev, mdev, tdev, hdev, ohdev
×
93
    data_type:
×
94
        "phase" or "freq"
×
95
    ci: float, defaults to scipy.special.erf(1/math.sqrt(2))
×
96
        for 1-sigma standard error set
×
97
        ci = scipy.special.erf(1/math.sqrt(2))
×
98
            = 0.68268949213708585
×
99

100
    Returns
×
101
    -------
×
102
    (dev_min, dev_max): (float, float)
×
103
        Confidence interval
×
104
    """
×
105
    # 1) noise ID
106
    dmax = 2
3✔
107
    if (dev_type == "hdev") or (dev_type == "ohdev"):
3✔
108
        dmax = 3
3✔
109
    alpha_int = autocorr_noise_id(
3✔
110
        x, int(af), data_type=data_type, dmin=0, dmax=dmax)[0]
3✔
111

112
    # 2) EDF (tes was 'is')
113
    if dev_type == "adev":
3✔
114
        edf = edf_greenhall(alpha=alpha_int, d=2, m=af, N=len(x),
3✔
115
                            overlapping=False, modified=False)
3✔
116
    elif dev_type == "oadev":
3✔
117
        edf = edf_greenhall(alpha=alpha_int, d=2, m=af, N=len(x),
3✔
118
                            overlapping=True, modified=False)
3✔
119
    elif (dev_type == "mdev") or (dev_type == "tdev"):
3✔
120
        edf = edf_greenhall(alpha=alpha_int, d=2, m=af, N=len(x),
3✔
121
                            overlapping=True, modified=True)
3✔
122
    elif dev_type == "hdev":
3✔
123
        edf = edf_greenhall(alpha=alpha_int, d=3, m=af, N=len(x),
3✔
124
                            overlapping=False, modified=False)
3✔
125
    elif dev_type == "ohdev":
3✔
126
        edf = edf_greenhall(alpha=alpha_int, d=3, m=af, N=len(x),
3✔
127
                            overlapping=True, modified=False)
3✔
128
    else:
×
129
        raise NotImplementedError
×
130

131
    # 3) confidence interval
132
    (low, high) = confidence_interval(dev, edf, ci)
3✔
133
    return (low, high)
3✔
134

135

136
########################################################################
137
# Noise Identification using R(n)
138

139

140
def rn(x, af, rate):
3✔
141
    """ R(n) ratio for noise identification
142

143
        ratio of MVAR to AVAR
144
    """
145
    (taus, devs, errs, ns) = at.adev(
146
        x, taus=[af*rate], data_type='phase', rate=rate)
147
    oadev_x = devs[0]
×
148
    (mtaus, mdevs, errs, ns) = at.mdev(
149
        x, taus=[af*rate], data_type='phase', rate=rate)
150
    mdev_x = mdevs[0]
×
151
    return pow(mdev_x/oadev_x, 2)
×
152

153

154
def rn_theory(af, b):
3✔
155
    """ R(n) ratio expected from theory for given noise type
156

157
        alpha = b + 2
158
    """
159
    # From IEEE1139-2008
160
    #   alpha   beta    ADEV_mu MDEV_mu Rn_mu
161
    #   -2      -4       1       1       0      Random Walk FM
162
    #   -1      -3       0       0       0      Flicker FM
163
    #    0      -2      -1      -1       0      White FM
164
    #    1      -1      -2      -2       0      Flicker PM
165
    #    2      0       -2      -3      -1      White PM
166

167
    # (a=-3 flicker walk FM)
168
    # (a=-4 random run FM)
169
    if b == 0:
×
170
        return pow(af, -1)
×
171
    elif b == -1:
×
172
        # f_h = 0.5/tau0  (assumed!)
173
        # af = tau/tau0
174
        # so f_h*tau = 0.5/tau0 * af*tau0 = 0.5*af
175
        avar = (1.038+3*np.log(2*np.pi*0.5*af)) / (4.0*pow(np.pi, 2))
×
176
        mvar = 3*np.log(256.0/27.0)/(8.0*pow(np.pi, 2))
×
177
        return mvar/avar
×
178
    else:
×
179
        return pow(af, 0)
×
180

181

182
def rn_boundary(af, b_hi):
3✔
183
    """
184
    R(n) ratio boundary for selecting between [b_hi-1, b_hi]
185
    alpha = b + 2
186
    """
187
    return np.sqrt(rn_theory(af, b_hi)*rn_theory(af, b_hi-1))  # geometric mean
×
188

189
########################################################################
190
# Noise Identification using B1
191

192

193
def b1(x, af, rate):
3✔
194
    """ B1 ratio for noise identification, [Barnes1974]_ and [Howe2000a]_
195
        (and bias correction?)
196

197
        ratio of Standard Variace to AVAR
198

199
    """
200
    (taus, devs, errs, ns) = at.adev(
201
        x, taus=[af*rate], data_type="phase", rate=rate)
202
    oadev_x = devs[0]
×
203
    avar = pow(oadev_x, 2.0)
×
204

205
    # variance of y, at given af
206
    y = np.diff(x)
×
207
    y_cut = np.array(y[:len(y)-(len(y) % af)])  # cut to length
×
208
    assert len(y_cut) % af == 0
×
209
    y_shaped = y_cut.reshape((int(len(y_cut)/af), af))
×
210
    y_averaged = np.average(y_shaped, axis=1)  # average
×
211
    var = np.var(y_averaged, ddof=1)
×
212

213
    return var/avar
×
214

215

216
def b1_theory(N, mu):
3✔
217
    """ Expected B1 ratio for given time-series length N and exponent mu
218

219
        FIXME: add reference (paper & link)
220

221
        The exponents are defined as
222
        S_y(f) = h_a f^alpha    (power spectrum of y)
223
        S_x(f) = g_b f^b        (power spectrum of x)
224
        bias = const * tau^mu
225

226
        and (b, alpha, mu) relate to eachother by:
227
        b    alpha   mu
228
        0    +2      -2
229
       -1    +1      -2   resolve between -2 cases with R(n)
230
       -2     0      -1
231
       -3    -1       0
232
       -4    -2      +1
233
       -5    -3      +2
234
       -6    -4      +3 for HDEV, by applying B1 to frequency data,
235
                        and add +2 to resulting mu
236
    """
237

238
    # see Table 3 of Howe 2000
239
    if mu == 2:
×
240
        return float(N)*(float(N)+1.0)/6.0
×
241
    elif mu == 1:
×
242
        return float(N)/2.0
×
243
    elif mu == 0:
×
244
        return N*np.log(N)/(2.0*(N-1.0)*np.log(2))
×
245
    elif mu == -1:
×
246
        return 1
×
247
    elif mu == -2:
×
248
        return (pow(N, 2)-1.0)/(1.5*N*(N-1.0))
×
249
    else:
×
250
        up = N*(1.0-pow(N, mu))
×
251
        down = 2*(N-1.0)*(1-pow(2.0, mu))
×
252
        return up/down
×
253

254
    assert False  # we should never get here
×
255

256

257
def b1_boundary(b_hi, N):
3✔
258
    """
259
    B1 ratio boundary for selecting between [b_hi-1, b_hi]
260
    alpha = b + 2
261
    """
262
    b_lo = b_hi-1
×
263
    b1_lo = b1_theory(N, b_to_mu(b_lo))
×
264
    b1_hi = b1_theory(N, b_to_mu(b_hi))
×
265
    if b1_lo >= -4:
×
266
        return np.sqrt(b1_lo*b1_hi)  # geometric mean
×
267
    else:
×
268
        return 0.5*(b1_lo+b1_hi)  # arithemtic mean
×
269

270

271
def b_to_mu(b):
3✔
272
    """
273
    return mu, parameter needed for B1 ratio function b1()
274
    alpha = b + 2
275
    """
276
    a = b + 2
×
277
    if a == +2:
×
278
        return -2
×
279
    elif a == +1:
×
280
        return -2
×
281
    elif a == 0:
×
282
        return -1
×
283
    elif a == -1:
×
284
        return 0
×
285
    elif a == -2:
×
286
        return 1
×
287
    elif a == -3:
×
288
        return 2
×
289
    elif a == -4:
×
290
        return 3
×
291
    assert False
×
292

293
########################################################################
294
# Noise Identification using ACF
295

296

297
def lag1_acf(x, detrend_deg=1):
3✔
298
    """ Lag-1 autocorrelation function
299
        as defined in [Riley2004]_ eqn (2)
300
        used by autocorr_noise_id()
301

302
        Parameters
303
        ----------
304
        x: numpy.array
305
            time-series
306
        Returns
307
        -------
308
        ACF: float
309
            Lag-1 autocorrelation for input time-series x
310

311
        Notes
312
        -----
313
        * a faster algorithm based on FFT might be better!?
314
        * numpy.corrcoeff() gives similar but not identical results.
315
            #c = np.corrcoef( np.array(x[:-lag]), np.array(x[lag:]) )
316
            #r1 = c[0,1] # lag-1 autocorrelation of x
317
    """
318
    mu = np.mean(x)
3✔
319
    a = 0
3✔
320
    b = 0
3✔
321
    for n in range(len(x)-1):
3✔
322
        a = a + (x[n]-mu)*(x[n+1]-mu)
3✔
323
    # for n in range(len(x)):
324
    for xn in x:
3✔
325
        b = b+pow(xn-mu, 2)
3✔
326
    return a/b
3✔
327

328

329
def autocorr_noise_id(x, af, data_type="phase", dmin=0, dmax=2):
3✔
330
    """ Lag-1 autocorrelation based noise identification
331

332
    Parameters
333
    ----------
334
    x: numpy.array
335
        phase or fractional frequency time-series data
336
        minimum recommended length is len(x)>30 roughly.
337
    af: int
338
        averaging factor
339
    data_type: string {'phase', 'freq'}
340
        "phase" for phase data in seconds
341
        "freq" for fractional frequency data
342
    dmin: int
343
        minimum required number of differentiations in the algorithm
344
    dmax: int
345
        maximum number of differentiations
346
        defaults to 2 for ADEV
347
        set to 3 for HDEV
348

349
    Returns
350
    -------
351
    alpha_int: int
352
        noise-slope as integer
353
    alpha: float
354
        noise-slope as float
355
    d: int
356
        number of differentiations of the time-series performed
357

358
    Notes
359
    -----
360
        http://www.stable32.com/Auto.pdf
361
        http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.503.9864&rep=rep1&type=pdf
362

363
    Reference [Riley2004]_.
364
    
365
    """
366
    d = 0  # number of differentiations
3✔
367
    if data_type == "phase":
3✔
368
        if af > 1:
3✔
369
            # x = scipy.signal.decimate(x, af, n=1, ftype='fir')
370
            x = x[0:len(x):af]  # decimate by averaging factor
3✔
371
            # x = scipy.signal.decimate(x, af, ftype='fir',)
372
            # resampled_len = int(len(x)/af)
373
            # x = scipy.signal.resample(x, resampled_len)
374

375
        x = detrend(x, deg=2)  # remove quadratic trend (freq offset and drift)
3✔
376
    elif data_type == "freq":
3✔
377
        # average by averaging factor
378
        y_cut = np.array(x[:len(x)-(len(x) % af)])  # cut to length
3✔
379
        assert len(y_cut) % af == 0
3✔
380
        y_shaped = y_cut.reshape((int(len(y_cut)/af), af))
3✔
381
        x = np.average(y_shaped, axis=1)  # average
3✔
382
        x = detrend(x, deg=1)  # remove frequency drift
3✔
383

384
    # require minimum length for time-series
385
    if len(x) < 30:
3✔
386
        print(("autocorr_noise_id() Don't know how to do noise-ID for"
3✔
387
               " time-series length= %d") % len(x))
3✔
388
        raise NotImplementedError
3✔
389

390
    while True:
1✔
391
        r1 = lag1_acf(x)
3✔
392
        rho = r1/(1.0+r1)
3✔
393
        if d >= dmin and (rho < 0.25 or d >= dmax):
3✔
394
            p = -2*(rho+d)
3✔
395
            # print r1
396
            # assert r1 < 0
397
            # assert r1 > -1.0/2.0
398
            phase_add2 = 0
3✔
399
            if data_type == "phase":
3✔
400
                phase_add2 = 2
3✔
401
            alpha = p+phase_add2
3✔
402
            alpha_int = int(-1.0*np.round(2*rho) - 2.0*d) + phase_add2
3✔
403
            # print "d=",d,"alpha=",p+2
404
            return alpha_int, alpha, d, rho
3✔
405
        else:
×
406
            x = np.diff(x)
3✔
407
            d = d + 1
3✔
408
    assert False  # we should not get here ever.
×
409

410

411
def detrend(x, deg=1):
3✔
412
    """
413
    remove polynomial from data.
414
    used by autocorr_noise_id()
415

416
    Parameters
417
    ----------
418
    x: numpy.array
419
        time-series
420
    deg: int
421
        degree of polynomial to remove from x
422

423
    Returns
424
    -------
425
    x_detrended: numpy.array
426
        detrended time-series
427
    """
428
    t = range(len(x))
3✔
429
    p = np.polyfit(t, x, deg)
3✔
430
    residual = x - np.polyval(p, t)
3✔
431
    return residual
3✔
432

433
########################################################################
434
# Equivalent Degrees of Freedom
435

436

437
def edf_greenhall_simple(alpha, d, m, S, F, N):
3✔
438
    """ Eqn (13) from [Greenhall2004]_ """
439
    L = m/F+m*d  # length of filter applied to phase samples
440
    M = 1 + np.floor(S*(N-L) / m)
441
    J = min(M, (d+1)*S)
442
    inv_edf = ((1.0/(pow(greenhall_sz(0, F, alpha, d), 2)*M)) *
443
               greenhall_BasicSum(J, M, S, F, alpha, d))
444
    return 1.0/inv_edf
445

446

447
def edf_greenhall(alpha, d, m, N,
448
                  overlapping=False, modified=False, verbose=False):
449
    """ returns Equivalent degrees of freedom
450

451
        Parameters
452
        ----------
453
        alpha: int
454
            noise type, +2...-4
455
        d: int
456
            1 first-difference variance
457
            2 Allan variance
458
            3 Hadamard variance
459
            require alpha+2*d>1
460
        m: int
461
            averaging factor
462
            tau = m*tau0 = m*(1/rate)
463
        N: int
464
            number of phase observations (length of time-series)
465
        overlapping: bool
466
            True for oadev, ohdev
467
        modified: bool
468
            True for mdev, tdev
469

470
        Returns
471
        -------
472
        edf: float
473
            Equivalent degrees of freedom
474

475
        Notes
476
        -----
477
        Reference [Greenhall2004]_.
478

479
        Used for the following deviations
480
        (see [Riley_CI]_ page 8)
481
        adev()
482
        oadev()
483
        mdev()
484
        tdev()
485
        hdev()
486
        ohdev()
487
    """
488

489
    if modified:
3✔
490
        F = 1  # F filter factor, 1 modified variance, m unmodified variance
3✔
491
    else:
×
492
        F = int(m)
3✔
493
    if overlapping:  # S stride factor, 1 nonoverlapped estimator,
3✔
494
        S = int(m)  # m overlapped estimator (estimator stride = tau/S )
3✔
495
    else:
×
496
        S = 1
3✔
497
    assert(alpha+2*d > 1.0)
3✔
498
    L = m/F+m*d  # length of filter applied to phase samples
3✔
499
    M = 1 + np.floor(S*(N-L) / m)
3✔
500
    J = min(M, (d+1)*S)
3✔
501
    J_max = 100
3✔
502
    r = M/S
3✔
503
    if int(F) == 1 and modified:  # case 1, modified variances, all alpha
3✔
504
        if J <= J_max:
3✔
505
            inv_edf = (1.0/(pow(greenhall_sz(0, 1, alpha, d), 2)*M)) * \
3✔
506
                       greenhall_BasicSum(J, M, S, 1, alpha, d)
3✔
507
            if verbose:
3✔
508
                print("case 1.1 edf= %3f" % float(1.0/inv_edf))
3✔
509
            return 1.0/inv_edf
3✔
510
        elif r > d+1:
3✔
511
            (a0, a1) = greenhall_table1(alpha, d)
3✔
512
            inv_edf = (1.0/r)*(a0-a1/r)
3✔
513
            if verbose:
3✔
514
                print("case 1.2 edf= %3f" % float(1.0/inv_edf))
3✔
515
            return 1.0/inv_edf
3✔
516
        else:
×
517
            m_prime = J_max/r
3✔
518
            inv_edf = ((1.0/(pow(greenhall_sz(0, F, alpha, d), 2)*J_max)) *
3✔
519
                       greenhall_BasicSum(J_max, J_max, m_prime, 1, alpha, d))
3✔
520
            if verbose:
3✔
521
                print("case 1.3 edf= %3f" % float(1.0/inv_edf))
3✔
522
            return 1.0/inv_edf
3✔
523
    elif int(F) == int(m) and int(alpha) <= 0 and not modified:
3✔
524
        # case 2, unmodified variances, alpha <= 0
525
        if J <= J_max:
3✔
526
            if m*(d+1) <= J_max:
3✔
527
                m_prime = m
3✔
528
                variant = "a"
3✔
529
            else:
×
530
                m_prime = float('inf')
3✔
531
                variant = "b"
3✔
532

533
            inv_edf = ((1.0/(pow(greenhall_sz(0, m_prime, alpha, d), 2)*M)) *
3✔
534
                       greenhall_BasicSum(J, M, S, m_prime, alpha, d))
3✔
535
            if verbose:
3✔
536
                print("case 2.1%s edf= %3f" % (variant, float(1.0/inv_edf)))
3✔
537
            return 1.0/inv_edf
3✔
538
        elif r > d+1:
3✔
539
            (a0, a1) = greenhall_table2(alpha, d)
3✔
540
            inv_edf = (1.0/r)*(a0-a1/r)
3✔
541
            if verbose:
3✔
542
                print("case 2.2 edf= %3f" % float(1.0/inv_edf))
3✔
543
            return 1.0/inv_edf
3✔
544
        else:
×
545
            m_prime = J_max/r
3✔
546
            inv_edf = (
2✔
547
                (1.0/(pow(greenhall_sz(0, float('inf'), alpha, d), 2)*J_max)) *
3✔
548
                greenhall_BasicSum(
3✔
549
                    J_max, J_max, m_prime, float('inf'), alpha, d))
3✔
550
            if verbose:
3✔
551
                print("case 2.3 edf= %3f" % float(1.0/inv_edf))
3✔
552
            return 1.0/inv_edf
3✔
553
    elif int(F) == int(m) and int(alpha) == 1 and not modified:
3✔
554
        # case 3, unmodified variances, alpha=1
555
        if J <= J_max:
3✔
556
            # note: m<1e6 to avoid roundoff
557
            inv_edf = ((1.0/(pow(greenhall_sz(0, m, 1, d), 2)*M)) *
3✔
558
                       greenhall_BasicSum(J, M, S, m, 1, d))
3✔
559
            if verbose:
3✔
560
                print("case 3.1 edf= %3f" % float(1.0/inv_edf))
3✔
561
            return 1.0/inv_edf
3✔
562
        elif r > d+1:
3✔
563
            (a0, a1) = greenhall_table2(alpha, d)
3✔
564
            (b0, b1) = greenhall_table3(alpha, d)
3✔
565
            inv_edf = (1.0/(pow(b0+b1*np.log(m), 2)*r))*(a0-a1/r)
3✔
566
            if verbose:
3✔
567
                print("case 3.2 edf= %3f" % float(1.0/inv_edf))
3✔
568
            return 1.0/inv_edf
3✔
569
        else:
×
570
            m_prime = J_max/r
×
571
            (b0, b1) = greenhall_table3(alpha, d)
×
572
            inv_edf = (
573
                (1.0/(pow(b0+b1*np.log(m), 2)*J_max)) *
574
                greenhall_BasicSum(J_max, J_max, m_prime, m_prime, 1, d))
575
            if verbose:
×
576
                print("case 3.3 edf= %3f" % float(1.0/inv_edf))
×
577
            return 1.0/inv_edf
×
578
    elif int(F) == int(m) and int(alpha) == 2 and not modified:
3✔
579
        # case 4, unmodified variances, alpha=2
580
        K = np.ceil(r)
3✔
581
        if K <= d:
3✔
582
            # FIXME: add formula from the paper here!
583
            raise NotImplementedError
×
584
        else:
×
585
            a0 = (scipy.special.binom(4*d, 2*d) /
3✔
586
                  pow(scipy.special.binom(2*d, d), 2))
3✔
587
            a1 = d/2.0
3✔
588
            inv_edf = (1.0/M)*(a0-a1/r)
3✔
589
            if verbose:
3✔
590
                print("case 4.2 edf= %3f" % float(1.0/inv_edf))
3✔
591
            return 1.0/inv_edf
3✔
592

593
    print("greenhall_edf() no matching case!")
×
594
    raise NotImplementedError
×
595

596

597
def greenhall_BasicSum(J, M, S, F, alpha, d):
3✔
598
    """ Eqn (10) from [Greenhall2004]_ """
599
    first = pow(greenhall_sz(0, F, alpha, d), 2)
600
    second = ((1-float(J)/float(M)) *
601
              pow(greenhall_sz(float(J)/float(S), F, alpha, d), 2))
602
    third = 0
603
    for j in range(1, int(J)):
604
        third += (2 * (1.0-float(j)/float(M)) *
605
                  pow(greenhall_sz(float(j) / float(S), F, alpha, d), 2))
606
    return first+second+third
607

608

609
def greenhall_sz(t, F, alpha, d):
610
    """ Eqn (9) from [Greenhall2004]_ """
611
    if d == 1:
3✔
612
        a = 2*greenhall_sx(t, F, alpha)
×
613
        b = greenhall_sx(t-1.0, F, alpha)
×
614
        c = greenhall_sx(t+1.0, F, alpha)
×
615
        return a-b-c
×
616
    elif d == 2:
3✔
617
        a = 6*greenhall_sx(t, F, alpha)
3✔
618
        b = 4*greenhall_sx(t-1.0, F, alpha)
3✔
619
        c = 4*greenhall_sx(t+1.0, F, alpha)
3✔
620
        dd = greenhall_sx(t-2.0, F, alpha)
3✔
621
        e = greenhall_sx(t+2.0, F, alpha)
3✔
622
        return a-b-c+dd+e
3✔
623
    elif d == 3:
3✔
624
        a = 20.0*greenhall_sx(t, F, alpha)
3✔
625
        b = 15.0*greenhall_sx(t-1.0, F, alpha)
3✔
626
        c = 15.0*greenhall_sx(t+1.0, F, alpha)
3✔
627
        dd = 6.0*greenhall_sx(t-2.0, F, alpha)
3✔
628
        e = 6.0*greenhall_sx(t+2.0, F, alpha)
3✔
629
        f = greenhall_sx(t-3.0, F, alpha)
3✔
630
        g = greenhall_sx(t+3.0, F, alpha)
3✔
631
        return a-b-c+dd+e-f-g
3✔
632

633
    assert(0)  # ERROR
×
634

635

636
def greenhall_sx(t, F, alpha):
3✔
637
    """ Eqn (8) from [Greenhall2004]_
638
    """
639
    if F == float('inf'):
3✔
640
        return greenhall_sw(t, alpha+2)
3✔
641
    a = 2*greenhall_sw(t, alpha)
3✔
642
    b = greenhall_sw(t-1.0/float(F), alpha)
3✔
643
    c = greenhall_sw(t+1.0/float(F), alpha)
3✔
644

645
    return pow(F, 2)*(a-b-c)
3✔
646

647

648
def greenhall_sw(t, alpha):
3✔
649
    """ Eqn (7) from [Greenhall2004]_
650
    """
651
    alpha = int(alpha)
3✔
652
    if alpha == 2:
3✔
653
        return -np.abs(t)
3✔
654
    elif alpha == 1:
3✔
655
        if t == 0:
3✔
656
            return 0
3✔
657
        else:
×
658
            return pow(t, 2)*np.log(np.abs(t))
3✔
659
    elif alpha == 0:
3✔
660
        return np.abs(pow(t, 3))
3✔
661
    elif alpha == -1:
3✔
662
        if t == 0:
×
663
            return 0
×
664
        else:
×
665
            return pow(t, 4)*np.log(np.abs(t))
×
666
    elif alpha == -2:
3✔
667
        return np.abs(pow(t, 5))
3✔
668
    elif alpha == -3:
×
669
        if t == 0:
×
670
            return 0
×
671
        else:
×
672
            return pow(t, 6)*np.log(np.abs(t))
×
673
    elif alpha == -4:
×
674
        return np.abs(pow(t, 7))
×
675

676
    assert(0)  # ERROR
×
677

678

679
def greenhall_table3(alpha, d):
3✔
680
    """ Table 3 from [Greenhall2004]_ """
681
    assert(alpha == 1)
682
    idx = d-1
683
    table3 = [(6.0, 4.0), (15.23, 12.0), (47.8, 40.0)]
684
    return table3[idx]
685

686

687
def greenhall_table2(alpha, d):
688
    """ Table 2 from [Greenhall2004]_ """
689
    row_idx = int(-alpha+2)  # map 2-> row0 and -4-> row6
3✔
690
    assert(row_idx in [0, 1, 2, 3, 4, 5])
3✔
691
    col_idx = int(d-1)
3✔
692
    table2 = [
2✔
693
        # alpha = +2:
694
        [(3.0/2.0, 1.0/2.0), (35.0/18.0, 1.0), (231.0/100.0, 3.0/2.0)],
3✔
695
        # alpha = +1:
696
        [(78.6, 25.2), (790.0, 410.0), (9950.0, 6520.0)],
3✔
697
        # alpha = 0:
698
        [(2.0/3.0, 1.0/6.0), (2.0/3.0, 1.0/3.0), (7.0/9.0, 1.0/2.0)],
3✔
699
        # alpha = -1:
700
        [(-1, -1), (0.852, 0.375), (0.997, 0.617)],
3✔
701
        # alpha = -2
702
        [(-1, -1), (1.079, 0.368), (1.033, 0.607)],
3✔
703
        # alpha = -3
704
        [(-1, -1), (-1, -1), (1.053, 0.553)],
3✔
705
        # alpha = -4
706
        [(-1, -1), (-1, -1), (1.302, 0.535)]]
3✔
707
    # print("table2 = ", table2[row_idx][col_idx])
708
    return table2[row_idx][col_idx]
3✔
709

710

711
def greenhall_table1(alpha, d):
3✔
712
    """ Table 1 from [Greenhall2004]_ """
713
    row_idx = int(-alpha+2)  # map 2-> row0 and -4-> row6
714
    col_idx = int(d-1)
715
    table1 = [
716
        # alpha = +2:
717
        [(2.0/3.0, 1.0/3.0), (7.0/9.0, 1.0/2.0), (22.0/25.0, 2.0/3.0)],
718
        # alpha = +1:
719
        [(0.840, 0.345), (0.997, 0.616), (1.141, 0.843)],
720
        # alpha = 0:
721
        [(1.079, 0.368), (1.033, 0.607), (1.184, 0.848)],
722
        # alpha = -1:
723
        [(-1, -1), (1.048, 0.534), (1.180, 0.816)],
724
        # alpha = -2
725
        [(-1, -1), (1.302, 0.535), (1.175, 0.777)],
726
        # alpha = -3
727
        [(-1, -1), (-1, -1), (1.194, 0.703)],
728
        # alpha = -4
729
        [(-1, -1), (-1, -1), (1.489, 0.702)]]
730
    # print("table1 = ", table1[row_idx][col_idx])
731
    return table1[row_idx][col_idx]
732

733

734
def edf_totdev(N, m, alpha):
735
    """ Equivalent degrees of freedom for Total Deviation
736
        FIXME: what is the right behavior for alpha outside 0,-1,-2?
737

738
        NIST [SP1065]_ page 41, Table 7
739
    """
740
    alpha = int(alpha)
3✔
741
    if alpha in [0, -1, -2]:
3✔
742
        # alpha  0 WFM
743
        # alpha -1 FFM
744
        # alpha -2 RWFM
745
        NIST_SP1065_table7 = [(1.50, 0.0), (1.17, 0.22), (0.93, 0.36)]
3✔
746
        (b, c) = NIST_SP1065_table7[int(abs(alpha))]
3✔
747
        return b*(float(N)/float(m))-c
3✔
748
    # alpha outside 0, -1, -2:
749
    return edf_simple(N, m, alpha)
×
750

751

752
def edf_mtotdev(N, m, alpha):
3✔
753
    """ Equivalent degrees of freedom for Modified Total Deviation
754

755
        NIST [SP1065]_ page 41, Table 8
756
    """
757
    assert(alpha in [2, 1, 0, -1, -2])
×
758
    NIST_SP1065_table8 = [(1.90, 2.1),
×
759
                          (1.20, 1.40),
×
760
                          (1.10, 1.2),
×
761
                          (0.85, 0.50),
×
762
                          (0.75, 0.31)]
×
763
    # (b, c) = NIST_SP1065_table8[ abs(alpha-2) ]
764
    (b, c) = NIST_SP1065_table8[abs(alpha-2)]
×
765
    edf = b*(float(N)/float(m))-c
×
766
    print("mtotdev b,c= ", (b, c), " edf=", edf)
×
767
    return edf
×
768

769

770
def edf_simple(N, m, alpha):
3✔
771
    """Equivalent degrees of freedom.
772
    Simple approximate formulae.
773

774
    Parameters
775
    ----------
776
    N : int
777
        the number of phase samples
778
    m : int
779
        averaging factor, tau = m * tau0
780
    alpha: int
781
        exponent of f for the frequency PSD:
782
        'wp' returns white phase noise.             alpha=+2
783
        'wf' returns white frequency noise.         alpha= 0
784
        'fp' returns flicker phase noise.           alpha=+1
785
        'ff' returns flicker frequency noise.       alpha=-1
786
        'rf' returns random walk frequency noise.   alpha=-2
787
        If the input is not recognized, it defaults to idealized, uncorrelated
788
        noise with (N-1) degrees of freedom.
789

790
    Notes
791
    -----
792
       See [Stein1985]_.
793
       
794
    Returns
795
    -------
796
    edf : float
797
        Equivalent degrees of freedom
798

799
    """
800

801
    N = float(N)
3✔
802
    m = float(m)
3✔
803
    if alpha in [2, 1, 0, -1, -2]:
3✔
804
        # NIST SP 1065, Table 5
805
        if alpha == +2:
3✔
806
            edf = (N + 1) * (N - 2*m) / (2 * (N - m))
3✔
807

808
        if alpha == 0:
3✔
809
            edf = (((3 * (N - 1) / (2 * m)) - (2 * (N - 2) / N)) *
×
810
                   ((4*pow(m, 2)) / ((4*pow(m, 2)) + 5)))
×
811

812
        if alpha == 1:
3✔
813
            a = (N - 1)/(2 * m)
×
814
            b = (2 * m + 1) * (N - 1) / 4
×
815
            edf = np.exp(np.sqrt(np.log(a) * np.log(b)))
×
816

817
        if alpha == -1:
3✔
818
            if m == 1:
×
819
                edf = 2 * (N - 2)/(2.3 * N - 4.9)
×
820
            if m >= 2:
×
821
                edf = 5 * N**2 / (4 * m * (N + (3 * m)))
×
822

823
        if alpha == -2:
3✔
824
            a = (N - 2) / (m * (N - 3)**2)
×
825
            b = (N - 1)**2
×
826
            c = 3 * m * (N - 1)
×
827
            d = 4 * m**2
×
828
            edf = a * (b - c + d)
×
829

830
    else:
×
831
        edf = (N - 1)
×
832
        print("Noise type not recognized."
×
833
              " Defaulting to N - 1 degrees of freedom.")
×
834

835
    return edf
3✔
836

837
########################################################################
838
# end of ci.py
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc