use crate::context::{Context, pown, truncation_order};
use crate::da::Da;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormType {
Infinity,
One,
Power(u32),
}
impl NormType {
pub const EUCLIDEAN: NormType = NormType::Power(2);
pub(crate) fn ityp(self) -> u32 {
match self {
NormType::Infinity => 0,
NormType::One => 1,
NormType::Power(p) => p,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Interval {
pub lo: f64,
pub hi: f64,
}
#[inline]
fn accumulate(acc: &mut f64, c: f64, ityp: u32) {
if ityp == 1 {
*acc += c.abs();
} else if ityp > 1 {
*acc += pown(c.abs(), ityp);
} else {
*acc = acc.max(c.abs());
}
}
#[inline]
fn finalize(acc: f64, ityp: u32) -> f64 {
if ityp > 1 {
acc.powf(1.0 / f64::from(ityp))
} else {
acc
}
}
impl Da {
pub fn abs(&self) -> f64 {
self.terms.iter().fold(0.0, |m, t| m.max(t.c.abs()))
}
pub fn norm(&self, ityp: NormType) -> f64 {
let ityp = ityp.ityp();
let mut acc = 0.0;
for t in &self.terms {
accumulate(&mut acc, t.c, ityp);
}
finalize(acc, ityp)
}
pub fn order_norm(&self, var: u32, ityp: NormType) -> Vec<f64> {
let ctx = &self.ctx;
let ityp = ityp.ityp();
let mut onorm = vec![0.0; ctx.nomax as usize + 1];
if var > ctx.nvmax {
log::warn!("DACE error 624: invalid independent variable {var} in order_norm");
return onorm;
}
if var == 0 {
for t in &self.terms {
let io = ctx.order_of(t.idx);
accumulate(&mut onorm[io as usize], t.c, ityp);
}
if ityp > 1 {
for v in onorm.iter_mut() {
*v = finalize(*v, ityp);
}
}
} else {
let mut jj = vec![0u32; ctx.nvmax as usize];
for t in &self.terms {
ctx.decode_into(t.idx, &mut jj);
accumulate(&mut onorm[jj[(var - 1) as usize] as usize], t.c, ityp);
}
if ityp > 1 {
for v in onorm.iter_mut() {
*v = finalize(*v, ityp);
}
}
}
onorm
}
pub fn estim_norm(&self, var: u32, ityp: NormType, nc: u32) -> Vec<f64> {
self.estim_norm_impl(var, ityp, nc, false).0
}
pub fn estim_norm_err(&self, var: u32, ityp: NormType, nc: u32) -> (Vec<f64>, Vec<f64>) {
self.estim_norm_impl(var, ityp, nc, true)
}
fn estim_norm_impl(
&self,
var: u32,
ityp: NormType,
nc: u32,
with_err: bool,
) -> (Vec<f64>, Vec<f64>) {
let ctx: Arc<Context> = self.ctx.clone();
let mut c = vec![0.0; nc as usize + 1];
let mut err = vec![0.0; nc.min(ctx.nomax) as usize + 1];
if ctx.nomax < 2 {
crate::error::dace_panic(651, "No estimate is possible");
}
let (eps, _) = crate::context::eps_nocut();
let onorm = self.order_norm(var, ityp);
let mut ai = [0.0f64; 2];
let mut xtx = [[0.0f64; 2]; 2];
for (i, &o) in onorm.iter().enumerate().skip(1) {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(o <= eps) {
let fi = i as f64;
xtx[0][0] += fi * fi;
xtx[0][1] -= fi;
xtx[1][1] += 1.0;
ai[0] += o.ln();
ai[1] += fi * o.ln();
}
}
if xtx[1][1] < 2.0 {
log::warn!("DACE info 163: norm estimate not possible; returning zeros");
return (c, err);
}
xtx[1][0] = xtx[0][1];
let det = xtx[0][0] * xtx[1][1] - xtx[0][1] * xtx[1][0];
let a = [
(ai[0] * xtx[0][0] + ai[1] * xtx[0][1]) / det,
(ai[0] * xtx[1][0] + ai[1] * xtx[1][1]) / det,
];
for (i, ci) in c.iter_mut().enumerate() {
*ci = (a[0] + a[1] * i as f64).exp();
}
if with_err {
for i in 0..err.len() {
let temp = onorm[i] - c[i];
err[i] = if temp > 0.0 { temp } else { 0.0 };
}
}
(c, err)
}
pub fn bound(&self) -> Interval {
let ctx = &self.ctx;
let mut lo = 0.0;
let mut hi = 0.0;
let mut terms = self.terms.iter().peekable();
if terms.peek().is_some_and(|t| t.idx == 0) {
let c = terms.next().unwrap().c;
lo = c;
hi = c;
}
let mut jj = vec![0u32; ctx.nvmax as usize];
for t in terms {
ctx.decode_into(t.idx, &mut jj);
let odd = jj.iter().any(|&e| e & 1 != 0);
if odd {
hi += t.c.abs();
lo -= t.c.abs();
} else if t.c > 0.0 {
hi += t.c;
} else {
lo += t.c;
}
}
Interval { lo, hi }
}
pub fn conv_radius(&self, eps: f64, ityp: NormType) -> f64 {
let ord = truncation_order();
let res = self.estim_norm(0, ityp, ord + 1);
(eps / res[(ord + 1) as usize]).powf(1.0 / f64::from(ord + 1))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::CONTEXT_LOCK;
#[test]
fn norms_and_bounds() {
let _g = CONTEXT_LOCK.lock();
crate::context::init(4, 2).unwrap();
let x = Da::variable(1);
let y = Da::variable(2);
let f = 1.0 + 2.0 * x.clone() + 3.0 * y.clone();
assert!((f.abs() - 3.0).abs() < 1e-15);
assert!((f.norm(NormType::One) - 6.0).abs() < 1e-15);
assert!((f.norm(NormType::EUCLIDEAN) - 14.0f64.sqrt()).abs() < 1e-15);
assert!((f.norm(NormType::Power(4)) - 98.0f64.powf(0.25)).abs() < 1e-14);
let on = f.order_norm(0, NormType::One);
assert_eq!(on.len(), 5);
assert!((on[0] - 1.0).abs() < 1e-15);
assert!((on[1] - 5.0).abs() < 1e-15);
let ov = f.order_norm(1, NormType::One);
assert!((ov[0] - 4.0).abs() < 1e-15); assert!((ov[1] - 2.0).abs() < 1e-15);
let b = (1.0 + x.clone()).bound();
assert!((b.lo - 0.0).abs() < 1e-15);
assert!((b.hi - 2.0).abs() < 1e-15);
let b2 = (x.clone() * x.clone() - 1.0).bound();
assert!((b2.lo + 1.0).abs() < 1e-15);
assert!((b2.hi - 0.0).abs() < 1e-15);
let e = crate::elementary::exp(&x);
let (c, err) = e.estim_norm_err(0, NormType::One, 6);
assert_eq!(c.len(), 7);
assert_eq!(err.len(), 5); assert!(c.iter().all(|v| v.is_finite()));
let r = e.conv_radius(1e-6, NormType::One);
assert!(r.is_finite() && r > 0.0);
}
}