fdars_core/depth/
spatial.rs1use crate::dim::Dim;
4use crate::helpers::simpsons_weights;
5use crate::iter_maybe_parallel;
6use crate::matrix::FdMatrix;
7#[cfg(feature = "parallel")]
8use rayon::iter::ParallelIterator;
9
10#[must_use = "expensive computation whose result should not be discarded"]
22pub fn functional_spatial(
23 data_obj: &FdMatrix,
24 data_ori: &FdMatrix,
25 argvals: Option<&[f64]>,
26 dim: Dim,
27) -> Vec<f64> {
28 match dim {
29 Dim::One => functional_spatial_impl(data_obj, data_ori, argvals),
30 Dim::Two => functional_spatial_impl(data_obj, data_ori, None),
31 }
32}
33
34fn functional_spatial_impl(
43 data_obj: &FdMatrix,
44 data_ori: &FdMatrix,
45 argvals: Option<&[f64]>,
46) -> Vec<f64> {
47 let nobj = data_obj.nrows();
48 let nori = data_ori.nrows();
49 let n_points = data_obj.ncols();
50
51 if nobj == 0 || nori == 0 || n_points == 0 {
52 return Vec::new();
53 }
54
55 let default_argvals: Vec<f64>;
57 let weights = if let Some(av) = argvals {
58 simpsons_weights(av)
59 } else {
60 default_argvals = (0..n_points)
61 .map(|i| i as f64 / (n_points - 1).max(1) as f64)
62 .collect();
63 simpsons_weights(&default_argvals)
64 };
65
66 iter_maybe_parallel!(0..nobj)
67 .map(|i| {
68 let mut sum_unit = vec![0.0; n_points];
69
70 for j in 0..nori {
71 let mut norm_sq = 0.0;
73 for t in 0..n_points {
74 let d = data_ori[(j, t)] - data_obj[(i, t)];
75 norm_sq += weights[t] * d * d;
76 }
77
78 let norm = norm_sq.sqrt();
79 if norm > 1e-10 {
80 let inv_norm = 1.0 / norm;
81 for t in 0..n_points {
82 sum_unit[t] += (data_ori[(j, t)] - data_obj[(i, t)]) * inv_norm;
83 }
84 }
85 }
86
87 let mut avg_norm_sq = 0.0;
89 for t in 0..n_points {
90 let avg = sum_unit[t] / nori as f64;
91 avg_norm_sq += weights[t] * avg * avg;
92 }
93
94 1.0 - avg_norm_sq.sqrt()
95 })
96 .collect()
97}
98
99fn kernel_pair_contribution(j: usize, k: usize, m1: &FdMatrix, m2: &[f64]) -> Option<f64> {
101 let denom_j_sq = 2.0 - 2.0 * m2[j];
102 if denom_j_sq < 1e-20 {
103 return None;
104 }
105 let denom_k_sq = 2.0 - 2.0 * m2[k];
106 if denom_k_sq < 1e-20 {
107 return None;
108 }
109 let denom = denom_j_sq.sqrt() * denom_k_sq.sqrt();
110 if denom <= 1e-20 {
111 return None;
112 }
113 let m_ijk = (1.0 + m1[(j, k)] - m2[j] - m2[k]) / denom;
114 if m_ijk.is_finite() {
115 Some(m_ijk)
116 } else {
117 None
118 }
119}
120
121fn kfsd_accumulate(m2: &[f64], m1: &FdMatrix, nori: usize) -> (f64, usize) {
128 let mut total_sum = 0.0;
129 let mut valid_count = 0;
130
131 for j in 0..nori {
133 if let Some(val) = kernel_pair_contribution(j, j, m1, m2) {
134 total_sum += val;
135 valid_count += 1;
136 }
137 }
138
139 for j in 0..nori {
141 for k in (j + 1)..nori {
142 if let Some(val) = kernel_pair_contribution(j, k, m1, m2) {
143 total_sum += 2.0 * val;
144 valid_count += 2;
145 }
146 }
147 }
148
149 (total_sum, valid_count)
150}
151
152fn kfsd_weighted(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64, weights: &[f64]) -> Vec<f64> {
155 let nobj = data_obj.nrows();
156 let nori = data_ori.nrows();
157 let n_points = data_obj.ncols();
158 let h_sq = h * h;
159
160 let m1_upper: Vec<(usize, usize, f64)> = iter_maybe_parallel!(0..nori)
162 .flat_map(|j| {
163 ((j + 1)..nori)
164 .map(|k| {
165 let mut sum = 0.0;
166 for t in 0..n_points {
167 let diff = data_ori[(j, t)] - data_ori[(k, t)];
168 sum += weights[t] * diff * diff;
169 }
170 (j, k, (-sum / h_sq).exp())
171 })
172 .collect::<Vec<_>>()
173 })
174 .collect();
175
176 let mut m1 = FdMatrix::zeros(nori, nori);
177 for j in 0..nori {
178 m1[(j, j)] = 1.0;
179 }
180 for (j, k, kval) in m1_upper {
181 m1[(j, k)] = kval;
182 m1[(k, j)] = kval;
183 }
184
185 let nori_f64 = nori as f64;
186
187 iter_maybe_parallel!(0..nobj)
188 .map(|i| {
189 let m2: Vec<f64> = (0..nori)
190 .map(|j| {
191 let mut sum = 0.0;
192 for t in 0..n_points {
193 let diff = data_obj[(i, t)] - data_ori[(j, t)];
194 sum += weights[t] * diff * diff;
195 }
196 (-sum / h_sq).exp()
197 })
198 .collect();
199
200 let (total_sum, valid_count) = kfsd_accumulate(&m2, &m1, nori);
201
202 if valid_count > 0 && total_sum >= 0.0 {
203 1.0 - total_sum.sqrt() / nori_f64
204 } else if total_sum < 0.0 {
205 1.0
206 } else {
207 0.0
208 }
209 })
210 .collect()
211}
212
213#[must_use = "expensive computation whose result should not be discarded"]
225pub fn kernel_functional_spatial(
226 data_obj: &FdMatrix,
227 data_ori: &FdMatrix,
228 argvals: Option<&[f64]>,
229 h: f64,
230 dim: Dim,
231) -> Vec<f64> {
232 match dim {
233 Dim::One => {
234 let default_argvals: Vec<f64>;
239 let av = match argvals {
240 Some(a) => a,
241 None => {
242 let n_points = data_obj.ncols();
243 default_argvals = (0..n_points)
244 .map(|i| i as f64 / (n_points - 1).max(1) as f64)
245 .collect();
246 &default_argvals
247 }
248 };
249 kernel_functional_spatial_1d_impl(data_obj, data_ori, av, h)
250 }
251 Dim::Two => kernel_functional_spatial_2d_impl(data_obj, data_ori, h),
252 }
253}
254
255fn kernel_functional_spatial_1d_impl(
259 data_obj: &FdMatrix,
260 data_ori: &FdMatrix,
261 argvals: &[f64],
262 h: f64,
263) -> Vec<f64> {
264 let nobj = data_obj.nrows();
265 let nori = data_ori.nrows();
266 let n_points = data_obj.ncols();
267
268 if nobj == 0 || nori == 0 || n_points == 0 {
269 return Vec::new();
270 }
271
272 let weights = simpsons_weights(argvals);
273 kfsd_weighted(data_obj, data_ori, h, &weights)
274}
275
276fn kernel_functional_spatial_2d_impl(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> Vec<f64> {
278 let nobj = data_obj.nrows();
279 let nori = data_ori.nrows();
280 let n_points = data_obj.ncols();
281
282 if nobj == 0 || nori == 0 || n_points == 0 {
283 return Vec::new();
284 }
285
286 let weights = vec![1.0; n_points];
287 kfsd_weighted(data_obj, data_ori, h, &weights)
288}