use crate::{
ArModel, EstimatorEvidence, EstimatorKind, EstimatorLimits, FrequencyGridPolicy, SignalError,
SpectrumEstimate, SpectrumScaling, SpectrumScalingKind,
spectrum_core::{bin_multiplier, resolve_grid},
spectrum_types::admit_work,
};
#[derive(Clone, Debug, PartialEq)]
pub struct MemSpectrumPlan {
pub sample_rate_hz: f64,
pub fft_len: usize,
pub grid: FrequencyGridPolicy,
pub denominator_floor: f64,
pub limits: EstimatorLimits,
}
impl MemSpectrumPlan {
pub fn new(sample_rate_hz: f64, fft_len: usize) -> Self {
Self {
sample_rate_hz,
fft_len,
grid: FrequencyGridPolicy::default(),
denominator_floor: 1.0e-18,
limits: EstimatorLimits::default(),
}
}
}
pub fn mem_spectrum(
model: &ArModel,
plan: &MemSpectrumPlan,
) -> Result<SpectrumEstimate, SignalError> {
if !plan.sample_rate_hz.is_finite() || plan.sample_rate_hz <= 0.0 || plan.fft_len == 0 {
return Err(SignalError::InvalidPolicy {
policy: "MEM spectrum grid",
reason: "positive finite sample rate and transform length are required",
});
}
if !plan.denominator_floor.is_finite() || plan.denominator_floor <= 0.0 {
return Err(SignalError::InvalidPolicy {
policy: "MEM denominator floor",
reason: "a finite positive floor is required",
});
}
let grid = resolve_grid(&plan.grid, plan.sample_rate_hz, plan.fft_len, plan.limits)?;
let work_units = u64::try_from(grid.frequency.len())
.ok()
.and_then(|bins| {
u64::try_from(model.coefficients.len() + 1)
.ok()?
.checked_mul(bins)
})
.ok_or(SignalError::InvalidPolicy {
policy: "MEM spectrum work",
reason: "work-unit arithmetic overflowed",
})?;
admit_work(work_units, plan.limits)?;
let mut power = Vec::with_capacity(grid.frequency.len());
for (index, frequency) in grid.frequency.iter().copied().enumerate() {
let omega = std::f64::consts::TAU * frequency / plan.sample_rate_hz;
let mut real = 1.0;
let mut imag = 0.0;
for (lag, coefficient) in model.coefficients.iter().copied().enumerate() {
let phase = -omega * (lag + 1) as f64;
real += coefficient * phase.cos();
imag += coefficient * phase.sin();
}
let denominator = real * real + imag * imag;
if !denominator.is_finite() || denominator < plan.denominator_floor {
return Err(SignalError::SingularModel { order: index });
}
power.push(
bin_multiplier(frequency, plan.sample_rate_hz, grid.side) * model.innovation_variance
/ (plan.sample_rate_hz * denominator),
);
}
let frequency_bins = grid.frequency.len();
Ok(SpectrumEstimate {
frequency: grid.frequency,
power,
scaling: SpectrumScaling {
kind: SpectrumScalingKind::Density,
sample_rate_hz: Some(plan.sample_rate_hz),
normalization_denominator: plan.sample_rate_hz,
one_sided: matches!(grid.side, crate::SpectrumSide::OneSided),
interior_bin_multiplier: if matches!(grid.side, crate::SpectrumSide::OneSided) {
2.0
} else {
1.0
},
},
evidence: EstimatorEvidence {
estimator: EstimatorKind::MaximumEntropy,
input_len: model.evidence.input_len,
fft_len: plan.fft_len,
segment_len: model.evidence.input_len,
segment_count: 1,
taper_count: 0,
frequency_bins,
work_units,
work_limit: plan.limits.max_work,
degrees_of_freedom: (model.evidence.input_len - model.evidence.effective_order) as f64,
frequency_grid: plan.grid.clone(),
window: None,
taper_concentrations: Vec::new(),
},
})
}