#![warn(missing_docs)]
use itertools::{Itertools, MinMaxResult};
use num_traits::{Float, FloatConst};
mod bands;
use bands::*;
mod barycentric;
use barycentric::*;
mod convf64;
pub use convf64::Convf64;
mod chebyshev;
use chebyshev::{chebyshev_nodes, compute_cheby_coefficients};
mod eigenvalues;
#[cfg(any(
feature = "faer-backend",
feature = "lapack-backend",
feature = "nalgebra-backend"
))]
pub use eigenvalues::DefaultEigenvalueBackend;
pub use eigenvalues::EigenvalueBackend;
#[cfg(feature = "faer-backend")]
pub use eigenvalues::FaerBackend;
#[cfg(feature = "lapack-backend")]
pub use eigenvalues::LapackBackend;
#[cfg(feature = "nalgebra-backend")]
pub use eigenvalues::NalgebraBackend;
pub mod error;
use error::Result;
mod extrema;
use extrema::*;
mod fir_types;
use fir_types::*;
#[cfg(feature = "lapack-backend")]
mod lapack;
#[cfg(feature = "lapack-backend")]
pub use lapack::{IsLapack, ToLapack};
pub mod order_estimates;
#[cfg(all(
feature = "python",
any(
feature = "faer-backend",
feature = "lapack-backend",
feature = "nalgebra-backend"
)
))]
mod python;
mod requirements;
pub use requirements::{BandSetting, Setting, constant, function, linear, pm_parameters};
mod types;
pub use types::{Band, DesignParameters, PMDesign, PMParameters, ParametersBuilder, Symmetry};
#[cfg(any(
feature = "lapack-backend",
feature = "faer-backend",
feature = "nalgebra-backend"
))]
pub fn pm_remez<T, P>(parameters: &P) -> Result<PMDesign<T>>
where
T: Float + FloatConst,
P: DesignParameters<T>,
DefaultEigenvalueBackend: EigenvalueBackend<T>,
{
pm_remez_with_backend(parameters, &DefaultEigenvalueBackend::default())
}
pub fn pm_remez_with_backend<T, P, B>(parameters: &P, eigenvalue_backend: &B) -> Result<PMDesign<T>>
where
T: Float + FloatConst,
P: DesignParameters<T>,
B: EigenvalueBackend<T>,
{
let bands = parameters.bands();
check_bands(bands)?;
let mut bands = sort_bands(bands);
let num_taps = parameters.num_taps();
let odd_length = num_taps % 2 != 0;
let desired_response = parameters.desired_response();
let symmetry = parameters.symmetry();
check_response(&bands, &desired_response, symmetry, odd_length)?;
adjust_bands(&mut bands, symmetry, odd_length);
for band in bands.iter_mut() {
band.convert_to_radians();
}
let desired = adjust_desired(desired_response, symmetry, odd_length);
let weights = parameters.weights();
let weights = adjust_weights(weights, symmetry, odd_length);
let num_functions = match (symmetry, odd_length) {
(Symmetry::Even, true) => num_taps / 2 + 1,
_ => num_taps / 2,
};
let mut extremal_freqs = initial_extremal_freqs(&bands, num_functions);
let mut x: Vec<T> = extremal_freqs.iter().map(|f| f.cos()).collect();
let mut wk: Vec<T> = compute_barycentric_weights(&x).collect();
let mut desired_x: Vec<T> = extremal_freqs.iter().map(|&f| desired(f)).collect();
let mut weights_x: Vec<T> = extremal_freqs.iter().map(|&f| weights(f)).collect();
let mut delta = compute_delta(&wk, &desired_x, &weights_x);
let mut yk: Vec<T> = compute_lagrange_abscisa(delta, &desired_x, &weights_x).collect();
let mut num_iterations = 0;
let mut flatness = T::zero();
let max_iterations = parameters.max_iterations();
let flatness_threshold = parameters.flatness_threshold();
let cheby_nodes: Vec<T> = chebyshev_nodes(parameters.chebyshev_proxy_degree()).collect();
for num_iter in 1..=max_iterations {
num_iterations = num_iter;
let bands_x: Vec<Interval<T>> = bands
.iter()
.rev()
.map(|b| Interval {
begin: b.end().cos(),
end: b.begin().cos(),
})
.collect();
let subintervals = subdivide(&x, &bands_x);
let upper_estimate_num_candidates =
subintervals.len() * (parameters.chebyshev_proxy_degree() + 1);
let mut remez_candidates: Vec<ExtremaCandidate<T>> =
Vec::with_capacity(upper_estimate_num_candidates);
remez_candidates.extend(subintervals.iter().flat_map(|interval| {
[
compute_extrema_candidate(interval.begin, &x, &wk, &yk, &desired, &weights),
compute_extrema_candidate(interval.end, &x, &wk, &yk, &desired, &weights),
]
.into_iter()
}));
for interval in &subintervals {
remez_candidates.extend(find_extrema_in_subinterval(
interval,
&cheby_nodes,
&x,
&wk,
&yk,
&desired,
&weights,
eigenvalue_backend,
)?);
}
debug_assert!(remez_candidates.len() <= upper_estimate_num_candidates);
remez_candidates.sort_unstable_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let remez_candidates = prune_extrema_candidates(&remez_candidates, num_functions + 1)?;
let MinMaxResult::MinMax(min_error, max_error) =
remez_candidates.iter().map(|a| a.error.abs()).minmax()
else {
panic!("remez_candidates has two few elements to obtain minmax()")
};
flatness = (max_error - min_error) / max_error;
for ((f, x0), candidate) in extremal_freqs
.iter_mut()
.zip(x.iter_mut())
.zip(remez_candidates.iter().rev())
{
*x0 = candidate.x;
*f = candidate.x.acos();
}
for (dst, src) in wk.iter_mut().zip(compute_barycentric_weights(&x)) {
*dst = src;
}
for (des, &f) in desired_x.iter_mut().zip(extremal_freqs.iter()) {
*des = desired(f);
}
for (wei, &f) in weights_x.iter_mut().zip(extremal_freqs.iter()) {
*wei = weights(f);
}
delta = compute_delta(&wk, &desired_x, &weights_x);
for (dst, src) in yk
.iter_mut()
.zip(compute_lagrange_abscisa(delta, &desired_x, &weights_x))
{
*dst = src
}
if flatness <= flatness_threshold {
break;
}
}
let mut ck: Vec<T> = {
let scale = T::PI() / T::from(num_functions - 1).unwrap();
(0..num_functions)
.map(|j| compute_freq_response((T::from(j).unwrap() * scale).cos(), &x, &wk, &yk))
.collect()
};
let ak = compute_cheby_coefficients(&mut ck);
for f in extremal_freqs.iter_mut() {
*f = *f / T::TAU();
}
Ok(PMDesign {
impulse_response: h_from_ak(&ak, num_taps, symmetry, odd_length),
weighted_error: delta.abs(),
extremal_freqs,
num_iterations,
flatness,
})
}