lockin_low_pass: compute magnitude noise analytically

This commit is contained in:
Matt Huszagh 2020-11-23 16:16:21 -08:00
parent 8ae20009d7
commit 8806feb423
1 changed files with 48 additions and 39 deletions

View File

@ -332,30 +332,30 @@ fn sampled_noise_amplitude(
/// ///
/// | sqrt((I+n*sin(x))**2 + (Q+n*cos(x))**2) - sqrt(I**2 + Q**2) | /// | sqrt((I+n*sin(x))**2 + (Q+n*cos(x))**2) - sqrt(I**2 + Q**2) |
/// ///
/// * I is the in-phase component of the part of the input signal we /// * I is the in-phase component of the portion of the input signal
/// care about (component of the input signal with the same frequency /// with the same frequency as the demodulation signal.
/// as the demodulation signal).
/// * Q is the quadrature component. /// * Q is the quadrature component.
/// * n is the total noise amplitude (from all contributions, after /// * n is the total noise amplitude (from all contributions, after
/// attenuation from filtering). /// attenuation from filtering).
/// * x is the phase of the demodulation signal and can be chosen to /// * x is the phase of the demodulation signal.
/// be anywhere in the range [0, 2pi) to maximize this expression.
/// ///
/// We need to find the demodulation phase (x) that maximizes this /// We need to find the demodulation phase (x) that maximizes this
/// expression. We could compute this, because we know I, Q, and n, /// expression. We can ignore the absolute value operation by also
/// but that's a fairly expensive computation and probably /// considering the expression minimum. The locations of the minimum
/// overkill. Instead, we can employ the heuristic that when |I|>>|Q|, /// and maximum can be computed analytically by finding the value of x
/// sin(x)=+-1 (+- denotes plus or minus) will maximize the error, /// when the derivative of this expression with respect to x is
/// when |Q|>>|I|, cos(x)=+-1 will maximize the error and when /// 0. When we solve this equation, we find:
/// |I|~|Q|, max,min(sin(x)+cos(x)) will maximize the error (this ///
/// occurs when sin(x)=cos(x)=+-1/sqrt(2)). Whether a positive or /// x = atan(I/Q)
/// negative noise term maximizes the error depends on the values and ///
/// signs of I and Q (for instance, when I,Q>0, negative noise terms /// It's worth noting that this solution is technically only valid
/// will maximize the error since the sqrt function is concave down), /// when cos(x)!=0 (i.e., x!=pi/2,-pi/2). However, this is not a
/// but the difference should be modest in each case so we should be /// problem because we only get these values when Q=0. Rust correctly
/// able to get a reasonably good approximation by using the positive /// computes atan(inf)=pi/2, which is precisely what we want because
/// noise case. We can use the maximum of all 3 cases as a rough /// x=pi/2 maximizes sin(x) and therefore also the noise effect.
/// approximation of the real maximum. ///
/// The other maximum or minimum is pi radians away from this
/// value.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -388,21 +388,17 @@ fn magnitude_noise(
.abs() .abs()
}; };
let mut max_noise: f64 = 0.; let phase = (in_phase_actual / quadrature_actual).atan();
for (in_phase_delta, quadrature_delta) in [ let max_noise_1 = noise(
(total_noise_amplitude, 0.), total_noise_amplitude * phase.sin(),
(0., total_noise_amplitude), total_noise_amplitude * phase.cos(),
( );
total_noise_amplitude / 2_f64.sqrt(), let max_noise_2 = noise(
total_noise_amplitude / 2_f64.sqrt(), total_noise_amplitude * (phase + PI).sin(),
), total_noise_amplitude * (phase + PI).cos(),
] );
.iter()
{
max_noise = max_noise.max(noise(*in_phase_delta, *quadrature_delta));
}
max_noise max_noise_1.max(max_noise_2)
} }
/// Compute the maximum phase deviation from the correct value due to /// Compute the maximum phase deviation from the correct value due to
@ -415,12 +411,25 @@ fn magnitude_noise(
/// See `magnitude_noise` for an explanation of the terms in this /// See `magnitude_noise` for an explanation of the terms in this
/// mathematical expression. /// mathematical expression.
/// ///
/// Similar to the heuristic used when computing the error in /// This expression is harder to compute analytically than the
/// `magnitude_noise`, we can use (sin(x)=+-1,cos(x)=0), /// expression in `magnitude_noise`. We could compute it numerically,
/// (sin(x)=0,cos(x)=+-1), and the value of x that maximizes /// but that's expensive. However, we can use heuristics to try to
/// |sin(x)-cos(x)| (when sin(x)=1/sqrt(2) and cos(x)=-1/sqrt(2), or /// guess the values of x that will maximize the noise
/// when the signs are flipped) as cases to test as an approximation /// effect. Intuitively, the difference will be largest when the
/// for the actual maximum value of this expression. /// Y-argument of the atan2 function (Q+n*cos(x)) is pushed in the
/// opposite direction of the noise effect on the X-argument (i.e.,
/// cos(x) and sin(x) have different signs). We can use:
///
/// * sin(x)=+-1 (+- denotes plus or minus), cos(x)=0,
/// * sin(x)=0, cos(x)=+-1, and
/// * the value of x that maximizes |sin(x)-cos(x)| (when
/// sin(x)=1/sqrt(2) and cos(x)=-1/sqrt(2), or when the signs are
/// flipped)
///
/// The first choice addresses cases in which |I|>>|Q|, the second
/// choice addresses cases in which |Q|>>|I|, and the third choice
/// addresses cases in which |I|~|Q|. We can test all of these cases
/// as an approximation for the real maximum.
/// ///
/// # Arguments /// # Arguments
/// ///