38 timeseries: np.ndarray,
40 max_clusters: Optional[int] =
None,
41) -> Tuple[np.ndarray, np.ndarray]:
43 Compute Allan variance at multiple averaging times τ = m·τ0.
46 timeseries: Voltage or ADC time series (1D)
47 tau0: Sampling interval (seconds)
48 max_clusters: Max cluster size (default: N/4)
51 tau_arr: Averaging times [τ0, 2τ0, 4τ0, ...]
52 sigma2_arr: Allan variance σ²_A(τ) at each τ
56 return np.array([tau0]), np.array([np.var(timeseries)])
58 max_m = max_clusters
or max(2, n // 4)
63 while m <= max_m
and n >= 3 * m:
65 num_triplets = (n - 2 * m) // m
70 [np.mean(timeseries[i * m : (i + 1) * m])
for i
in range(num_triplets + 2)]
74 diffs = clusters[2:] - 2 * clusters[1:-1] + clusters[:-2]
75 sigma2 = 0.5 * np.mean(diffs**2)
79 sigma2_list.append(sigma2)
80 m = 2 * m
if m >= 2
else m + 1
82 return np.array(tau_list), np.array(sigma2_list)
87 sigma2_arr: np.ndarray,
88) -> NoiseCoefficients:
90 Fit noise model: σ²_A(τ) = Q²/(2τ) + 2·ln(2)·B² + K²·τ/6 + R²·τ²/20
92 Uses least-squares on log-scale for stability.
95 NoiseCoefficients(Q, B, K, R, tau_min, sigma_min)
103 tau_min=tau_arr[0]
if len(tau_arr) > 0
else 1.0,
104 sigma_min=np.sqrt(sigma2_arr[0])
if len(sigma2_arr) > 0
else 1e-3,
111 np.full_like(tau_arr, 2 * np.log(2)),
119 beta, _, _, _ = np.linalg.lstsq(X, y, rcond=
None)
120 Q = np.sqrt(
max(beta[0], 1e-12))
121 B = np.sqrt(
max(beta[1], 1e-12))
122 K = np.sqrt(
max(beta[2], 1e-12))
123 R = np.sqrt(
max(beta[3], 1e-12))
124 except np.linalg.LinAlgError:
125 Q, B, K, R = 1e-3, 1e-4, 1e-5, 1e-6
129 np.sqrt(3 * Q**2 / (K**2 + 1e-20))
if K > 1e-10
else tau_arr[len(tau_arr) // 2]
131 tau_min = np.clip(tau_min, tau_arr.min(), tau_arr.max())
134 + 2 * np.log(2) * B**2
136 + R**2 * tau_min**2 / 20
139 return NoiseCoefficients(Q=Q, B=B, K=K, R=R, tau_min=tau_min, sigma_min=sigma_min)
144 coeffs: NoiseCoefficients,
145 voltage: float = 0.0,
146 sigma_base: float = 1e-3,
149 Measurement uncertainty σ_meas(v, τ) from paper Section 7.
151 σ²_meas = σ²_base + Q²/(2τ) + 2·ln(2)·B² + K²·τ/6 + α_v·v²
154 tau: Averaging time (seconds)
155 coeffs: Noise coefficients from fit_noise_coefficients
156 voltage: Optional voltage for heteroscedastic term
157 sigma_base: Base noise floor
160 σ_meas in same units as input (PSI if voltage→PSI calibrated)
163 var += coeffs.Q**2 / (2 * tau)
164 var += 2 * np.log(2) * coeffs.B**2
165 var += coeffs.K**2 * tau / 6
166 var += 1e-4 * voltage**2
167 return np.sqrt(
max(var, 1e-12))