use alloc::vec::Vec;
use core::f64::consts::PI;
const BANDPASS: usize = 1;
const DIFFERENTIATOR: usize = 2;
const HILBERT: usize = 3;
const NEGATIVE: bool = false;
const POSITIVE: bool = true;
const GRIDDENSITY: usize = 16;
const MAXITERATIONS: usize = 40;
#[allow(clippy::too_many_arguments)]
fn create_dense_grid(
r: usize,
numtaps: usize,
numband: usize,
bands: &[f64],
des: &[f64],
weight: &[f64],
symmetry: bool,
griddensity: usize,
gridsize: usize,
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let delf = 0.5 / (griddensity as f64 * r as f64);
let grid0 = if (symmetry == NEGATIVE) && (delf > bands[0]) {
delf
} else {
bands[0]
};
let mut grid = vec![0.; gridsize];
let mut d = vec![0.; gridsize];
let mut w = vec![0.; gridsize];
let mut j: usize = 0;
for band in 0..numband {
let mut lowf = if band == 0 { grid0 } else { bands[2 * band] };
let highf = bands[2 * band + 1];
let k = ((highf - lowf) / delf).round();
for i in 0..(k as usize) {
d[j] = des[2 * band] + i as f64 * (des[2 * band + 1] - des[2 * band]) / (k - 1.);
w[j] = weight[band];
grid[j] = lowf;
lowf += delf;
j += 1;
}
grid[j - 1] = highf;
}
if (symmetry == NEGATIVE) && (grid[gridsize - 1] > (0.5 - delf)) && !numtaps.is_multiple_of(2) {
grid[gridsize - 1] = 0.5 - delf;
}
(grid, d, w)
}
fn initial_guess(r: usize, gridsize: usize) -> Vec<usize> {
(0..(r + 1)).map(|i| i * (gridsize - 1) / r).collect()
}
fn calc_parms(
r: usize,
ext: &[usize],
grid: &[f64],
d: &[f64],
w: &[f64],
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let x: Vec<f64> = ext.iter().map(|i| (2. * PI * grid[*i]).cos()).collect();
let ld = (r - 1) / 15 + 1;
let ad: Vec<f64> = (0..(r + 1))
.map(|i| {
let mut denom: f64 = 1.;
let xi = x[i];
for j in 0..ld {
for k in (j..(r + 1)).step_by(ld) {
if k != i {
denom *= 2.0 * (xi - x[k]);
}
}
}
if denom.abs() < 0.00001 {
denom = 0.00001;
}
1.0 / denom
})
.collect();
let mut numer: f64 = 0.;
let mut denom: f64 = 0.;
let mut sign: f64 = 1.;
for i in 0..(r + 1) {
numer += ad[i] * d[ext[i]];
denom += sign * ad[i] / w[ext[i]];
sign = -sign;
}
let delta = numer / denom;
sign = 1.;
let mut y = vec![0.; r + 1];
for i in 0..(r + 1) {
y[i] = d[ext[i]] - sign * delta / w[ext[i]];
sign = -sign;
}
(ad, x, y)
}
fn compute_a(freq: f64, r: usize, ad: &[f64], x: &[f64], y: &[f64]) -> f64 {
let mut denom: f64 = 0.;
let mut numer: f64 = 0.;
let xc = (2. * PI * freq).cos();
for i in 0..(r + 1) {
let mut c = xc - x[i];
if c.abs() < 1.0e-7 {
numer = y[i];
denom = 1.;
break;
}
c = ad[i] / c;
denom += c;
numer += c * y[i];
}
numer / denom
}
#[allow(clippy::too_many_arguments)]
fn calc_error(
r: usize,
ad: &[f64],
x: &[f64],
y: &[f64],
gridsize: usize,
grid: &[f64],
d: &[f64],
w: &[f64],
) -> Vec<f64> {
(0..gridsize)
.map(|i| w[i] * (d[i] - compute_a(grid[i], r, ad, x, y)))
.collect()
}
fn search(r: usize, ext: &mut [usize], gridsize: usize, e: &[f64]) -> i8 {
let mut k = 0;
let mut found_ext = vec![0_usize; 2 * r];
if ((e[0] > 0.0) && (e[0] > e[1])) || ((e[0] < 0.0) && (e[0] < e[1])) {
found_ext[k] = 0;
k += 1;
}
for i in 1..(gridsize - 1) {
if ((e[i] >= e[i - 1]) && (e[i] > e[i + 1]) && (e[i] > 0.0))
|| ((e[i] <= e[i - 1]) && (e[i] < e[i + 1]) && (e[i] < 0.0))
{
if k >= 2 * r {
return -3;
}
found_ext[k] = i;
k += 1;
}
}
let j = gridsize - 1;
if ((e[j] > 0.0) && (e[j] > e[j - 1])) || ((e[j] < 0.0) && (e[j] < e[j - 1])) {
if k >= 2 * r {
return -3;
}
found_ext[k] = j;
k += 1;
}
if k < r + 1 {
return -2;
}
let mut extra = k.checked_sub(r + 1).unwrap();
while extra > 0 {
let mut up = e[found_ext[0]] > 0.0;
let mut l: usize = 0;
let mut alt: bool = true;
for j in 1..k {
if e[found_ext[j]].abs() < e[found_ext[l]].abs() {
l = j;
}
if up && (e[found_ext[j]] < 0.0) {
up = false;
} else if !up && (e[found_ext[j]] > 0.0) {
up = true;
} else {
alt = false;
break;
}
}
if alt && (extra == 1) {
if e[found_ext[k - 1]].abs() < e[found_ext[0]].abs() {
l = k - 1;
}
else {
l = 0;
}
}
for j in l..(k - 1) {
found_ext[j] = found_ext[j + 1];
assert!(found_ext[j] < gridsize);
}
k -= 1;
extra -= 1;
}
for i in 0..(r + 1) {
assert!(found_ext[i] < gridsize);
ext[i] = found_ext[i];
}
0
}
fn freq_sample(n_coeffs: usize, a: &[f64], symm: bool) -> Vec<f64> {
let m = (n_coeffs - 1) as f64 / 2.0;
if symm == POSITIVE {
if !n_coeffs.is_multiple_of(2) {
(0..n_coeffs)
.map(|n| {
let mut val = a[0];
let x = 2. * PI * (n as f64 - m) / n_coeffs as f64;
for (k, &a_k) in a.iter().enumerate().take(m as usize).skip(1) {
val += 2.0 * a_k * (x * k as f64).cos();
}
val / n_coeffs as f64
})
.collect()
} else {
(0..n_coeffs)
.map(|n| {
let mut val = a[0];
let x = 2. * PI * (n as f64 - m) / n_coeffs as f64;
for (k, &a_k) in a.iter().enumerate().take(n_coeffs / 2 - 1).skip(1) {
val += 2.0 * a_k * (x * k as f64).cos();
}
val / n_coeffs as f64
})
.collect()
}
} else if !n_coeffs.is_multiple_of(2) {
(0..n_coeffs)
.map(|n| {
let mut val = 0.;
let x = 2. * PI * (n as f64 - m) / n_coeffs as f64;
for (k, &a_k) in a.iter().enumerate().take(m as usize).skip(1) {
val += 2.0 * a_k * (x * k as f64).sin();
}
val / n_coeffs as f64
})
.collect()
} else {
(0..n_coeffs)
.map(|n| {
let mut val = a[n_coeffs / 2] * (PI * (n as f64 - m)).sin();
let x = 2. * PI * (n as f64 - m) / n_coeffs as f64;
for (k, &a_k) in a.iter().enumerate().take(n_coeffs / 2 - 1).skip(1) {
val += 2.0 * a_k * (x * k as f64).sin();
}
val / n_coeffs as f64
})
.collect()
}
}
fn is_done(ext: &[usize], e: &[f64]) -> bool {
let min = ext
.iter()
.map(|i| e[*i].abs())
.min_by(|a, b| a.total_cmp(b))
.unwrap();
let max = ext
.iter()
.map(|i| e[*i].abs())
.max_by(|a, b| a.total_cmp(b))
.unwrap();
((max - min) / max) < 0.0001
}
fn remez(
numtaps: usize,
numband: usize,
bands: &[f64],
des: &[f64],
weight: &[f64],
filter_type: usize,
griddensity: usize,
) -> (Vec<f64>, i8) {
let symmetry = if filter_type == BANDPASS {
POSITIVE
} else {
NEGATIVE
};
let mut r = numtaps / 2;
if !numtaps.is_multiple_of(2) && (symmetry == POSITIVE) {
r += 1;
}
let mut gridsize: usize = 0;
for i in 0..numband {
gridsize += (2. * r as f64 * griddensity as f64 * (bands[2 * i + 1] - bands[2 * i])).round()
as usize;
}
if symmetry == NEGATIVE {
gridsize -= 1;
}
let (grid, mut d, mut w) = create_dense_grid(
r,
numtaps,
numband,
bands,
des,
weight,
symmetry,
griddensity,
gridsize,
);
let mut ext = initial_guess(r, gridsize);
if filter_type == DIFFERENTIATOR {
for i in 0..gridsize {
if d[i] > 0.0001 {
w[i] /= grid[i];
}
}
}
if symmetry == POSITIVE {
if numtaps.is_multiple_of(2) {
for i in 0..gridsize {
let c = (PI * grid[i]).cos();
d[i] /= c;
w[i] *= c;
}
}
} else if !numtaps.is_multiple_of(2) {
for i in 0..gridsize {
let c = (2. * PI * grid[i]).sin();
d[i] /= c;
w[i] *= c;
}
} else {
for i in 0..gridsize {
let c = (PI * grid[i]).sin();
d[i] /= c;
w[i] *= c;
}
}
let mut num_iter: usize = 0;
for iter in 0..MAXITERATIONS {
let (ad, x, y) = calc_parms(r, &ext, &grid, &d, &w);
let e = calc_error(r, &ad, &x, &y, gridsize, &grid, &d, &w);
let err = search(r, &mut ext, gridsize, &e);
if err > 0 {
return (vec![0.], err);
}
for &ext_idx in &ext {
assert!(ext_idx < gridsize);
}
num_iter = iter;
if is_done(&ext, &e) {
break;
}
}
let (ad, x, y) = calc_parms(r, &ext, &grid, &d, &w);
let taps: Vec<f64> = (0..(numtaps / 2))
.map(|i| {
let c: f64 = if symmetry == POSITIVE {
if !numtaps.is_multiple_of(2) {
1.
} else {
(PI * i as f64 / numtaps as f64).cos()
}
} else if !numtaps.is_multiple_of(2) {
(2. * PI * i as f64 / numtaps as f64).sin()
} else {
(PI * i as f64 / numtaps as f64).sin()
};
compute_a(i as f64 / numtaps as f64, r, &ad, &x, &y) * c
})
.collect();
let h = freq_sample(numtaps, &taps, symmetry);
(h, if num_iter < MAXITERATIONS { 0 } else { -1 })
}
fn punt(msg: &str) {
warn!("pm_remez: {}", msg);
panic!("{}", msg);
}
pub fn pm_remez(
order: usize,
arg_bands: &[f64],
arg_response: &[f64],
arg_weight: &[f64],
filter_type: &str,
grid_density: Option<usize>,
) -> Vec<f64> {
let numtaps = order + 1;
if numtaps < 4 {
punt("number of taps must be >= 3");
}
let numbands = arg_bands.len() / 2;
if numbands < 1 || arg_bands.len() % 2 == 1 {
punt("must have an even number of band edges");
}
for i in 1..arg_bands.len() {
if arg_bands[i] < arg_bands[i - 1] {
punt("band edges must be nondecreasing");
}
}
if arg_bands[0] < 0. || arg_bands[arg_bands.len() - 1] > 1. {
punt("band edges must be in the range [0,1]");
}
let bands: Vec<f64> = arg_bands.iter().map(|x| x / 2.).collect();
if arg_response.len() != arg_bands.len() {
punt("must have one response magnitude for each band edge");
}
let response = arg_response;
let weight = if !arg_weight.is_empty() {
if arg_weight.len() != numbands {
punt("need one weight for each band [=length(band)/2]");
}
arg_weight.to_vec()
} else {
vec![1.0_f64; numbands]
};
let itype: usize = if filter_type == "bandpass" {
BANDPASS
} else if filter_type == "differentiator" {
DIFFERENTIATOR
} else if filter_type == "hilbert" {
HILBERT
} else {
punt(&format!("unknown ftype '{filter_type}'"));
0
};
let grid_density = grid_density.unwrap_or(GRIDDENSITY);
if grid_density < 16 {
punt("grid_density is too low; must be >= 16");
}
let (coeff, err) = remez(
numtaps,
numbands,
&bands,
response,
&weight,
itype,
grid_density,
);
if err == -1 {
punt("failed to converge");
}
if err == -2 {
punt("insufficient extremals -- cannot continue");
}
if err == -3 {
punt("too many extremals -- cannot continue");
}
coeff
}