1use crate::context::{Context, pown, truncation_order};
9use crate::da::Da;
10use std::sync::Arc;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum NormType {
19 Infinity,
21 One,
23 Power(u32),
25}
26
27impl NormType {
28 pub const EUCLIDEAN: NormType = NormType::Power(2);
30
31 pub(crate) fn ityp(self) -> u32 {
32 match self {
33 NormType::Infinity => 0,
34 NormType::One => 1,
35 NormType::Power(p) => p,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct Interval {
43 pub lo: f64,
45 pub hi: f64,
47}
48
49#[inline]
52fn accumulate(acc: &mut f64, c: f64, ityp: u32) {
53 if ityp == 1 {
54 *acc += c.abs();
55 } else if ityp > 1 {
56 *acc += pown(c.abs(), ityp);
57 } else {
58 *acc = acc.max(c.abs());
59 }
60}
61
62#[inline]
64fn finalize(acc: f64, ityp: u32) -> f64 {
65 if ityp > 1 {
66 acc.powf(1.0 / f64::from(ityp))
67 } else {
68 acc
69 }
70}
71
72impl Da {
73 pub fn abs(&self) -> f64 {
76 self.terms.iter().fold(0.0, |m, t| m.max(t.c.abs()))
77 }
78
79 pub fn norm(&self, ityp: NormType) -> f64 {
81 let ityp = ityp.ityp();
82 let mut acc = 0.0;
83 for t in &self.terms {
84 accumulate(&mut acc, t.c, ityp);
85 }
86 finalize(acc, ityp)
87 }
88
89 pub fn order_norm(&self, var: u32, ityp: NormType) -> Vec<f64> {
95 let ctx = &self.ctx;
96 let ityp = ityp.ityp();
97 let mut onorm = vec![0.0; ctx.nomax as usize + 1];
98 if var > ctx.nvmax {
99 log::warn!("DACE error 624: invalid independent variable {var} in order_norm");
100 return onorm;
101 }
102 if var == 0 {
103 for t in &self.terms {
104 let io = ctx.order_of(t.idx);
105 accumulate(&mut onorm[io as usize], t.c, ityp);
106 }
107 if ityp > 1 {
108 for v in onorm.iter_mut() {
109 *v = finalize(*v, ityp);
110 }
111 }
112 } else {
113 let mut jj = vec![0u32; ctx.nvmax as usize];
114 for t in &self.terms {
115 ctx.decode_into(t.idx, &mut jj);
116 accumulate(&mut onorm[jj[(var - 1) as usize] as usize], t.c, ityp);
117 }
118 if ityp > 1 {
119 for v in onorm.iter_mut() {
120 *v = finalize(*v, ityp);
121 }
122 }
123 }
124 onorm
125 }
126
127 pub fn estim_norm(&self, var: u32, ityp: NormType, nc: u32) -> Vec<f64> {
138 self.estim_norm_impl(var, ityp, nc, false).0
139 }
140
141 pub fn estim_norm_err(&self, var: u32, ityp: NormType, nc: u32) -> (Vec<f64>, Vec<f64>) {
144 self.estim_norm_impl(var, ityp, nc, true)
145 }
146
147 fn estim_norm_impl(
148 &self,
149 var: u32,
150 ityp: NormType,
151 nc: u32,
152 with_err: bool,
153 ) -> (Vec<f64>, Vec<f64>) {
154 let ctx: Arc<Context> = self.ctx.clone();
155 let mut c = vec![0.0; nc as usize + 1];
156 let mut err = vec![0.0; nc.min(ctx.nomax) as usize + 1];
157 if ctx.nomax < 2 {
158 crate::error::dace_panic(651, "No estimate is possible");
159 }
160
161 let (eps, _) = crate::context::eps_nocut();
162 let onorm = self.order_norm(var, ityp);
163
164 let mut ai = [0.0f64; 2];
166 let mut xtx = [[0.0f64; 2]; 2];
167 for (i, &o) in onorm.iter().enumerate().skip(1) {
168 #[allow(clippy::neg_cmp_op_on_partial_ord)]
170 if !(o <= eps) {
171 let fi = i as f64;
172 xtx[0][0] += fi * fi;
173 xtx[0][1] -= fi;
174 xtx[1][1] += 1.0;
175 ai[0] += o.ln();
176 ai[1] += fi * o.ln();
177 }
178 }
179
180 if xtx[1][1] < 2.0 {
181 log::warn!("DACE info 163: norm estimate not possible; returning zeros");
182 return (c, err);
183 }
184
185 xtx[1][0] = xtx[0][1];
186 let det = xtx[0][0] * xtx[1][1] - xtx[0][1] * xtx[1][0];
187
188 let a = [
189 (ai[0] * xtx[0][0] + ai[1] * xtx[0][1]) / det,
190 (ai[0] * xtx[1][0] + ai[1] * xtx[1][1]) / det,
191 ];
192
193 for (i, ci) in c.iter_mut().enumerate() {
194 *ci = (a[0] + a[1] * i as f64).exp();
195 }
196
197 if with_err {
198 for i in 0..err.len() {
199 let temp = onorm[i] - c[i];
200 err[i] = if temp > 0.0 { temp } else { 0.0 };
201 }
202 }
203 (c, err)
204 }
205
206 pub fn bound(&self) -> Interval {
211 let ctx = &self.ctx;
212 let mut lo = 0.0;
213 let mut hi = 0.0;
214 let mut terms = self.terms.iter().peekable();
215
216 if terms.peek().is_some_and(|t| t.idx == 0) {
218 let c = terms.next().unwrap().c;
219 lo = c;
220 hi = c;
221 }
222
223 let mut jj = vec![0u32; ctx.nvmax as usize];
224 for t in terms {
225 ctx.decode_into(t.idx, &mut jj);
226 let odd = jj.iter().any(|&e| e & 1 != 0);
227 if odd {
228 hi += t.c.abs();
229 lo -= t.c.abs();
230 } else if t.c > 0.0 {
231 hi += t.c;
232 } else {
233 lo += t.c;
234 }
235 }
236 Interval { lo, hi }
237 }
238
239 pub fn conv_radius(&self, eps: f64, ityp: NormType) -> f64 {
242 let ord = truncation_order();
243 let res = self.estim_norm(0, ityp, ord + 1);
244 (eps / res[(ord + 1) as usize]).powf(1.0 / f64::from(ord + 1))
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::test_support::CONTEXT_LOCK;
252
253 #[test]
254 fn norms_and_bounds() {
255 let _g = CONTEXT_LOCK.lock();
256 crate::context::init(4, 2).unwrap();
257 let x = Da::variable(1);
258 let y = Da::variable(2);
259 let f = 1.0 + 2.0 * x.clone() + 3.0 * y.clone();
260
261 assert!((f.abs() - 3.0).abs() < 1e-15);
262 assert!((f.norm(NormType::One) - 6.0).abs() < 1e-15);
263 assert!((f.norm(NormType::EUCLIDEAN) - 14.0f64.sqrt()).abs() < 1e-15);
264 assert!((f.norm(NormType::Power(4)) - 98.0f64.powf(0.25)).abs() < 1e-14);
265
266 let on = f.order_norm(0, NormType::One);
267 assert_eq!(on.len(), 5);
268 assert!((on[0] - 1.0).abs() < 1e-15);
269 assert!((on[1] - 5.0).abs() < 1e-15);
270 let ov = f.order_norm(1, NormType::One);
271 assert!((ov[0] - 4.0).abs() < 1e-15); assert!((ov[1] - 2.0).abs() < 1e-15); let b = (1.0 + x.clone()).bound();
276 assert!((b.lo - 0.0).abs() < 1e-15);
277 assert!((b.hi - 2.0).abs() < 1e-15);
278 let b2 = (x.clone() * x.clone() - 1.0).bound();
280 assert!((b2.lo + 1.0).abs() < 1e-15);
281 assert!((b2.hi - 0.0).abs() < 1e-15);
282
283 let e = crate::elementary::exp(&x);
285 let (c, err) = e.estim_norm_err(0, NormType::One, 6);
286 assert_eq!(c.len(), 7);
287 assert_eq!(err.len(), 5); assert!(c.iter().all(|v| v.is_finite()));
289
290 let r = e.conv_radius(1e-6, NormType::One);
292 assert!(r.is_finite() && r > 0.0);
293 }
294}