1use crate::agreement::Mapping;
3use crate::buffer::Element;
4use crate::error::{Error, Kind};
5use crate::gather::{self, Steps};
6use crate::layout::{Layout, count};
7use crate::linalg::{Inner, Orientation, PlusTimes, Scalar};
8use crate::reduce::{self, Reduction, Sum};
9use crate::{Float, Native};
10use num_traits::{Float as _, One as _, Zero as _};
11use std::borrow::Cow;
12use std::ops::Range;
13
14pub struct Axes { pub dims: [usize; 3], pub layout: Layout, shape: Vec<usize>, steps: Option<Vec<Steps>> }
16
17impl Axes {
18 pub fn new(layout: &Layout, axes: &[usize]) -> Result<Self, Error> {
19 let shape = layout.shape();
20 let mut axes = axes.to_vec();
21 axes.sort_unstable();
22 axes.dedup();
23 if axes.last().is_some_and(|&a| a >= shape.len()) { return Err(Error::new(Kind::Index, "reduction axis is out of range")); }
24 let remaining: Vec<_> = (0..shape.len()).filter(|a| !axes.contains(a)).collect();
25 let result = layout.axes(remaining.iter().copied());
26 let (first, end) = axes.first().zip(axes.last()).map_or((0, 0), |(&a, &b)| (a, b + 1));
27 if end - first == axes.len() {
28 return Ok(Self { dims: [count(&shape[..first]), count(&shape[first..end]), count(&shape[end..])], layout: result, shape: shape.to_vec(), steps: None
29 });
30 }
31 let mut destinations = vec![0; shape.len()];
32 for (to, &from) in remaining.iter().chain(&axes).enumerate() { destinations[from] = to; }
33 let plan = crate::structure::transpose(layout, &destinations)?;
34 Ok(Self { dims: [remaining.iter().map(|&a| shape[a]).product(), axes.iter().map(|&a| shape[a]).product(), 1], layout: result, shape: shape.to_vec(),
35 steps: Some(plan.steps) })
36 }
37
38 pub fn read<'a, T: Element>(&self, data: &'a [T]) -> Cow<'a, [T]> {
39 let Some(steps) = &self.steps else { return Cow::Borrowed(data) };
40 let mut out = Vec::with_capacity(data.len());
41 gather::walk(&mut out, data, 0, steps, T::FILL, gather::Same);
42 Cow::Owned(out)
43 }
44
45 fn plan(&self, mapping: &Mapping) -> (usize, Vec<Steps>) {
47 let frame: Vec<usize> = self.steps.as_ref().map_or_else(|| self.shape.clone(), |steps| steps.iter().map(Steps::len).collect());
48 let ordered = match &self.steps {
49 Some(steps) => {
50 mapping.composed(&Mapping::Steps { base: 0, steps: steps.clone().into() }, &self.shape, &frame, true).expect("a table always composes")
51 }
52 None => mapping.clone(),
53 };
54 let (base, steps) = ordered.to_walk(&frame);
55 (base, steps.into_owned())
56 }
57}
58
59pub fn mean<T: Scalar>(data: &[T], dims: [usize; 3]) -> Vec<T> where Sum: Reduction<T, Total = T> {
61 let mut out = reduce::along::<T, Sum>(data, dims).0;
62 let count = T::Real::narrow(dims[1] as f64);
63 for v in &mut out { *v = v.div_real(count); }
64 out
65}
66
67#[inline]
69fn mapped<F: Copy, R: Reduction<F>>(dims: [usize; 3], f: impl Fn(Range<usize>, &mut Vec<F>) + Sync) -> Vec<R::Total> {
70 let mut totals = vec![R::unit(); dims[0] * dims[2]];
71 if dims[1] == 0 || totals.is_empty() { return totals; }
72 reduce::fold_pieces::<F, R>(&mut totals, dims, &|range, fold| {
73 let mut work = Vec::with_capacity(crate::CHUNK);
74 for start in (range.start..range.end).step_by(crate::CHUNK) {
75 let end = range.end.min(start + crate::CHUNK);
76 work.clear();
77 f(start..end, &mut work);
78 fold(start, reduce::Part::Items(&work));
79 }
80 });
81 totals
82}
83
84#[inline]
85fn values<T: Copy + Sync, F: Copy, R: Reduction<F>>(data: &[T], dims: [usize; 3], f: impl Fn(T) -> F + Sync) -> Vec<R::Total> {
86 mapped::<F, R>(dims, |range, out| out.extend(data[range].iter().map(|&x| f(x))))
87}
88
89#[inline(always)]
90fn square<T: Scalar>(x: T) -> T::Real { x.re().algebraic_mul(x.re()).algebraic_add(x.im().algebraic_mul(x.im())) }
91
92pub fn variance<T: Scalar>(data: &[T], dims: [usize; 3], ddof: usize) -> Vec<T::Real> where Sum: Reduction<T, Total = T> {
94 let means = mean(data, dims);
95 let [_, len, inner] = dims;
96 let mut out = mapped::<T::Real, Sum>(dims, |range, out| {
97 let mut start = range.start;
98 while start < range.end {
99 if inner == 1 {
100 let m = means[start / len];
101 let end = range.end.min((start / len + 1) * len);
102 out.extend(data[start..end].iter().map(#[inline(always)] |&x| square(x - m)));
103 start = end;
104 } else {
105 let m = start / (len * inner) * inner + start % inner;
106 let end = range.end.min((start / inner + 1) * inner);
107 out.extend(data[start..end].iter().enumerate().map(#[inline(always)] |(k, &x)| square(x - means[m + k])));
108 start = end;
109 }
110 }
111 });
112 let count = T::Real::narrow(len.saturating_sub(ddof) as f64);
113 for v in &mut out { *v = if ddof >= len { T::Real::nan() } else { *v / count }; }
114 out
115}
116
117pub fn stddev<T: Scalar>(data: &[T], dims: [usize; 3], ddof: usize) -> Vec<T::Real> where Sum: Reduction<T, Total = T> {
119 let mut out = variance(data, dims, ddof);
120 for v in &mut out { *v = v.sqrt(); }
121 out
122}
123
124pub fn norm<T: Scalar>(data: &[T], dims: [usize; 3], p: f64) -> Vec<T::Real> {
126 if p == 1.0 { return values::<_, _, Sum>(data, dims, #[inline(always)] |x| x.modulus()); }
127 if p == 0.0 {
128 return values::<_, _, Sum>(data, dims, #[inline(always)] |x| if x == T::zero() { T::Real::zero() } else { T::Real::one() });
129 }
130 if p == f64::INFINITY { return values::<_, _, reduce::Max>(data, dims, #[inline(always)] |x| x.modulus()); }
131 if p == f64::NEG_INFINITY { return values::<_, _, reduce::Min>(data, dims, #[inline(always)] |x| x.modulus()); }
132 if p == 2.0 { return reduce::along::<T, reduce::Squares>(data, dims).0.into_iter().map(|v| T::Real::narrow(v.sqrt())).collect(); }
133 let mut out = values::<_, _, Sum>(data, dims, #[inline(always)] |x| crate::pow(x.modulus(), T::Real::narrow(p)));
134 for v in &mut out { *v = crate::pow(*v, T::Real::narrow(1.0 / p)); }
135 out
136}
137
138pub fn average<T: Scalar + Element>(data: &[T], weights: &[T], axes: &Axes, mappings: [&Mapping; 2]) -> Vec<T> where Sum: Reduction<T, Total = T> {
140 let [(xbase, xsteps), (wbase, wsteps)] = mappings.map(|mapping| axes.plan(mapping));
141 let [(xbase, xplan), (wbase, wplan)] = [Steps::simplify(xbase, &xsteps), Steps::simplify(wbase, &wsteps)];
142 let read = |x: &[T], base, plan: &[Cow<Steps>], range: Range<usize>, out: &mut Vec<T>| {
143 gather::walk_plan(out, x, base, plan, T::FILL, range.start, range.len(), gather::Same)
144 };
145 let mut sums = mapped::<_, Sum>(axes.dims, |range, out| {
146 read(data, xbase, &xplan, range.clone(), out);
147 let mut w = Vec::with_capacity(range.len());
148 read(weights, wbase, &wplan, range, &mut w);
149 for (x, w) in out.iter_mut().zip(w) { *x = *x * w; }
150 });
151 let totals = mapped::<_, Sum>(axes.dims, |range, out| read(weights, wbase, &wplan, range, out));
152 for (sum, weight) in sums.iter_mut().zip(totals) { *sum = sum.quotient(weight); }
153 sums
154}
155
156pub fn covariance<T: Scalar>(data: &[T], [rows, cols]: [usize; 2], rowvar: bool, ddof: usize, correlation: bool) -> Vec<T> where Sum: Reduction<T, Total = T>,
158 PlusTimes: Inner<T, Out = T> {
159 let (variables, observations, dims) = if rowvar { (rows, cols, [rows, cols, 1]) } else { (cols, rows, [1, rows, cols]) };
160 let means = mean(data, dims);
161 let mut centered = Vec::with_capacity(data.len());
162 if rowvar { centered.extend_from_slice(data); } else {
163 centered.resize(data.len(), T::zero());
164 crate::linalg::transpose_into(data, cols, &mut centered, rows, [rows, cols], |x| x);
165 }
166 if observations > 0 { for (row, &m) in centered.chunks_mut(observations).zip(&means) { for x in row { *x = *x - m; } } }
167 let conjugate = if !T::COMPLEX { Cow::Borrowed(¢ered[..]) } else { Cow::Owned(centered.iter().map(|&x| x.conj()).collect()) };
168 let mut out = PlusTimes.inner_ops(¢ered, &conjugate, [Orientation::Normal, Orientation::Transpose], [variables, observations, variables]);
169 let count = T::Real::narrow(observations.saturating_sub(ddof) as f64);
170 if correlation {
171 let scales: Vec<_> = (0..variables).map(|j| out[j * (variables + 1)].re().sqrt()).collect();
172 for (ij, x) in out.iter_mut().enumerate() { *x = x.div_real(scales[ij / variables] * scales[ij % variables]); }
173 } else { for x in &mut out { *x = if ddof >= observations { T::from_real(T::Real::nan()) } else { x.div_real(count) }; } }
174 out
175}
176
177pub fn histogram_edges<F: Native>(data: &[F], bins: usize, range: Option<(f64, f64)>) -> Result<Vec<F>, Error> {
179 if bins == 0 { return Err(Error::new(Kind::Domain, "histogram needs a positive bin count")); }
180 let (mut lo, mut hi) = range.unwrap_or_else(|| if data.is_empty() { (0.0, 1.0) } else { (reduce::min(data).wide(), reduce::max(data).wide()) });
181 if lo == hi {
182 lo -= 0.5;
183 hi += 0.5;
184 }
185 if !lo.is_finite() || !hi.is_finite() || lo >= hi { return Err(Error::new(Kind::Domain, "histogram needs a finite increasing range")); }
186 Ok((0..=bins).map(|j| F::narrow(lo + (hi - lo) * (j as f64 / bins as f64))).collect())
187}
188
189pub fn histogram<F: Native>(data: &[F], edges: &[F]) -> Result<Vec<i64>, Error> {
191 if edges.len() < 2 || edges.windows(2).any(|p| p[0].is_nan() || p[0] >= p[1]) || edges.last().is_some_and(|v| v.is_nan()) {
192 return Err(Error::new(Kind::Domain, "histogram needs at least two strictly increasing bin edges"));
193 }
194 let bins = crate::search::bins(&crate::tolerant::order_keys(edges), &crate::tolerant::order_keys(data));
195 let n = edges.len() - 1;
196 let mut out = vec![0; n];
197 for (&x, b) in data.iter().zip(bins) { let b = b as usize; if b > 0 && b <= n { out[b - 1] += 1; } else if x == edges[n] { out[n - 1] += 1; } }
198 Ok(out)
199}