use crate::{util::*, *};
use alloc::{format, vec::Vec};
use core::f64::consts::{PI, TAU};
pub type Efd1 = Efd<1>;
pub type Efd2 = Efd<2>;
pub type Efd3 = Efd<3>;
#[inline]
pub const fn harmonic(is_open: bool, len: usize) -> usize {
if is_open {
len * 2
} else {
len
}
}
#[inline]
pub const fn harmonic_nyquist(is_open: bool, len: usize) -> usize {
harmonic(is_open, len) / 2
}
pub fn get_norm_t<C, const D: usize>(curve: C, is_open: bool) -> Vec<f64>
where
C: Curve<D>,
U<D>: EfdDim<D>,
{
PathSig::new(curve, is_open).t
}
#[derive(Clone)]
pub struct PathSig<const D: usize>
where
U<D>: EfdDim<D>,
{
pub curve: Vec<[f64; D]>,
pub t: Vec<f64>,
pub geo: GeoVar<Rot<D>, D>,
}
impl<const D: usize> PathSig<D>
where
U<D>: EfdDim<D>,
{
pub fn new<C>(curve: C, is_open: bool) -> Self
where
C: Curve<D>,
{
let (Efd { mut coeffs, geo }, mut t) = Efd::get_all_unnorm(curve.as_curve(), is_open, 2);
let geo = geo * U::norm_coeff(&mut coeffs, Some(&mut t));
let curve = geo.inverse().transform(curve);
Self { curve, t, geo }
}
pub fn as_t(&self) -> &[f64] {
&self.t
}
pub fn as_geo(&self) -> &GeoVar<Rot<D>, D> {
&self.geo
}
}
#[derive(Clone)]
pub struct Efd<const D: usize>
where
U<D>: EfdDim<D>,
{
coeffs: Coeffs<D>,
geo: GeoVar<Rot<D>, D>,
}
impl<const D: usize> Efd<D>
where
U<D>: EfdDim<D>,
{
pub fn from_parts_unchecked(coeffs: Coeffs<D>, geo: GeoVar<Rot<D>, D>) -> Self {
assert!(!coeffs.is_empty(), "the harmonic must be greater than 0");
Self { coeffs, geo }
}
pub fn from_coeffs_unchecked(coeffs: Coeffs<D>) -> Self {
Self::from_parts_unchecked(coeffs, GeoVar::identity())
}
pub fn from_curve<C>(curve: C, is_open: bool) -> Self
where
C: Curve<D>,
{
let harmonic = harmonic(is_open, curve.len());
Self::from_curve_harmonic(curve, is_open, harmonic).fourier_power_anaysis(None)
}
pub fn from_curve_nyquist<C>(curve: C, is_open: bool) -> Self
where
C: Curve<D>,
{
let harmonic = harmonic_nyquist(is_open, curve.len());
Self::from_curve_harmonic(curve, is_open, harmonic).fourier_power_anaysis(None)
}
pub fn from_curve_harmonic<C>(curve: C, is_open: bool, harmonic: usize) -> Self
where
C: Curve<D>,
{
Self::from_curve_harmonic_unnorm(curve, is_open, harmonic).normalized()
}
pub fn from_curve_harmonic_unnorm<C>(curve: C, is_open: bool, harmonic: usize) -> Self
where
C: Curve<D>,
{
Self::get_all_unnorm(curve, is_open, harmonic).0
}
#[track_caller]
fn get_all_unnorm<C>(curve: C, is_open: bool, harmonic: usize) -> (Self, Vec<f64>)
where
C: Curve<D>,
{
debug_assert!(harmonic != 0, "harmonic must not be 0");
debug_assert!(curve.len() > 2, "the curve length must greater than 2");
let (t, coeffs, geo) = U::get_coeff(curve.as_curve(), is_open, harmonic, None);
(Self { coeffs, geo }, t)
}
pub fn fourier_power_anaysis(mut self, threshold: impl Into<Option<f64>>) -> Self {
self.fpa_inplace(threshold);
self
}
pub fn fpa_inplace(&mut self, threshold: impl Into<Option<f64>>) {
let lut = self.coeffs.iter().map(|m| m.map(pow2).sum()).collect();
self.set_harmonic(fourier_power_anaysis(lut, threshold.into()));
}
pub fn set_harmonic(&mut self, harmonic: usize) {
assert!(harmonic > 0, "harmonic must geater than 0");
self.coeffs.truncate(harmonic);
}
pub fn normalized(self) -> Self {
let Self { mut coeffs, geo } = self;
let geo = geo * U::norm_coeff(&mut coeffs, None);
Self { coeffs, geo }
}
pub fn into_inner(self) -> (Coeffs<D>, GeoVar<Rot<D>, D>) {
(self.coeffs, self.geo)
}
pub fn coeffs(&self) -> &[Kernel<D>] {
&self.coeffs
}
pub fn coeff(&self, harmonic: usize) -> &Kernel<D> {
&self.coeffs[harmonic]
}
pub fn coeffs_iter(&self) -> impl Iterator<Item = &Kernel<D>> {
self.coeffs.iter()
}
pub fn coeffs_iter_mut(&mut self) -> impl Iterator<Item = &mut Kernel<D>> {
self.coeffs.iter_mut()
}
pub fn as_geo(&self) -> &GeoVar<Rot<D>, D> {
&self.geo
}
pub fn as_geo_mut(&mut self) -> &mut GeoVar<Rot<D>, D> {
&mut self.geo
}
pub fn is_open(&self) -> bool {
self.coeffs[0].column(1).sum() == 0.
}
#[inline]
pub fn harmonic(&self) -> usize {
self.coeffs.len()
}
pub fn is_valid(&self) -> bool {
self.harmonic() > 0 && self.coeffs_iter().flatten().all(|x| x.is_finite())
}
pub fn err(&self, rhs: &Self) -> f64 {
self.l1_err(rhs)
}
pub fn err_sig(&self, sig: &PathSig<D>) -> f64 {
core::iter::zip(self.recon_norm_by(&sig.t), &sig.curve)
.map(|(a, b)| a.l2_err(b))
.fold(0., f64::max)
}
pub fn reverse_inplace(&mut self) {
for m in &mut self.coeffs {
let mut m = m.column_mut(1);
m *= -1.;
}
}
pub fn reversed(mut self) -> Self {
self.reverse_inplace();
self
}
pub fn recon(&self, n: usize) -> Vec<[f64; D]> {
let mut curve = self.recon_norm(n);
self.geo.transform_inplace(&mut curve);
curve
}
pub fn recon_norm(&self, n: usize) -> Vec<[f64; D]> {
let t = if self.is_open() { PI } else { TAU };
let iter = (0..n).map(|i| i as f64 / (n - 1) as f64 * t);
U::reconstruct(&self.coeffs, iter)
}
pub fn recon_by(&self, t: &[f64]) -> Vec<[f64; D]> {
let mut curve = U::reconstruct(&self.coeffs, t.iter().copied());
self.geo.transform_inplace(&mut curve);
curve
}
pub fn recon_norm_by(&self, t: &[f64]) -> Vec<[f64; D]> {
U::reconstruct(&self.coeffs, t.iter().copied())
}
}
impl<const D: usize> core::fmt::Debug for Efd<D>
where
U<D>: EfdDim<D>,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_valid() {
f.debug_struct(&format!("Efd{D}"))
.field("is_open", &self.is_open())
.field("harmonic", &self.harmonic())
.field("geo", &self.geo)
.field("coeff", &CoeffFmt(&self.coeffs))
.finish()
} else {
f.debug_struct(&format!("Efd{D}"))
.field("is_valid", &false)
.finish()
}
}
}
impl<const D: usize> core::fmt::Debug for PosedEfd<D>
where
U<D>: EfdDim<D>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(&format!("PosedEfd{D}"))
.field("harmonic", &self.harmonic())
.finish()
}
}
struct CoeffFmt<'a, const D: usize>(&'a Coeffs<D>);
impl<const D: usize> core::fmt::Debug for CoeffFmt<'_, D> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let entries = self.0.iter().map(|c| c.iter().copied().collect::<Vec<_>>());
f.debug_list().entries(entries).finish()
}
}
pub(crate) fn fourier_power_anaysis(lut: Vec<f64>, threshold: Option<f64>) -> usize {
let threshold = threshold.unwrap_or(0.9999);
assert!((0.0..1.0).contains(&threshold), "threshold must in 0..1");
let lut = cumsum(na::Matrix1xX::from_vec(lut));
let target = lut[lut.len() - 1] * threshold;
match lut
.as_slice()
.binary_search_by(|x| x.partial_cmp(&target).unwrap())
{
Ok(h) | Err(h) => h + 1,
}
}