fdars_core/depth/
spatial.rs1use crate::helpers::simpsons_weights;
4use crate::iter_maybe_parallel;
5use crate::matrix::FdMatrix;
6#[cfg(feature = "parallel")]
7use rayon::iter::ParallelIterator;
8
9#[must_use = "expensive computation whose result should not be discarded"]
18pub fn functional_spatial_1d(
19 data_obj: &FdMatrix,
20 data_ori: &FdMatrix,
21 argvals: Option<&[f64]>,
22) -> Vec<f64> {
23 let nobj = data_obj.nrows();
24 let nori = data_ori.nrows();
25 let n_points = data_obj.ncols();
26
27 if nobj == 0 || nori == 0 || n_points == 0 {
28 return Vec::new();
29 }
30
31 let default_argvals: Vec<f64>;
33 let weights = if let Some(av) = argvals {
34 simpsons_weights(av)
35 } else {
36 default_argvals = (0..n_points)
37 .map(|i| i as f64 / (n_points - 1).max(1) as f64)
38 .collect();
39 simpsons_weights(&default_argvals)
40 };
41
42 iter_maybe_parallel!(0..nobj)
43 .map(|i| {
44 let mut sum_unit = vec![0.0; n_points];
45
46 for j in 0..nori {
47 let mut norm_sq = 0.0;
49 for t in 0..n_points {
50 let d = data_ori[(j, t)] - data_obj[(i, t)];
51 norm_sq += weights[t] * d * d;
52 }
53
54 let norm = norm_sq.sqrt();
55 if norm > 1e-10 {
56 let inv_norm = 1.0 / norm;
57 for t in 0..n_points {
58 sum_unit[t] += (data_ori[(j, t)] - data_obj[(i, t)]) * inv_norm;
59 }
60 }
61 }
62
63 let mut avg_norm_sq = 0.0;
65 for t in 0..n_points {
66 let avg = sum_unit[t] / nori as f64;
67 avg_norm_sq += weights[t] * avg * avg;
68 }
69
70 1.0 - avg_norm_sq.sqrt()
71 })
72 .collect()
73}
74
75#[must_use = "expensive computation whose result should not be discarded"]
77pub fn functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
78 functional_spatial_1d(data_obj, data_ori, None)
79}
80
81fn kernel_pair_contribution(j: usize, k: usize, m1: &FdMatrix, m2: &[f64]) -> Option<f64> {
83 let denom_j_sq = 2.0 - 2.0 * m2[j];
84 if denom_j_sq < 1e-20 {
85 return None;
86 }
87 let denom_k_sq = 2.0 - 2.0 * m2[k];
88 if denom_k_sq < 1e-20 {
89 return None;
90 }
91 let denom = denom_j_sq.sqrt() * denom_k_sq.sqrt();
92 if denom <= 1e-20 {
93 return None;
94 }
95 let m_ijk = (1.0 + m1[(j, k)] - m2[j] - m2[k]) / denom;
96 if m_ijk.is_finite() {
97 Some(m_ijk)
98 } else {
99 None
100 }
101}
102
103fn kfsd_accumulate(m2: &[f64], m1: &FdMatrix, nori: usize) -> (f64, usize) {
110 let mut total_sum = 0.0;
111 let mut valid_count = 0;
112
113 for j in 0..nori {
115 if let Some(val) = kernel_pair_contribution(j, j, m1, m2) {
116 total_sum += val;
117 valid_count += 1;
118 }
119 }
120
121 for j in 0..nori {
123 for k in (j + 1)..nori {
124 if let Some(val) = kernel_pair_contribution(j, k, m1, m2) {
125 total_sum += 2.0 * val;
126 valid_count += 2;
127 }
128 }
129 }
130
131 (total_sum, valid_count)
132}
133
134fn kfsd_weighted(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64, weights: &[f64]) -> Vec<f64> {
137 let nobj = data_obj.nrows();
138 let nori = data_ori.nrows();
139 let n_points = data_obj.ncols();
140 let h_sq = h * h;
141
142 let m1_upper: Vec<(usize, usize, f64)> = iter_maybe_parallel!(0..nori)
144 .flat_map(|j| {
145 ((j + 1)..nori)
146 .map(|k| {
147 let mut sum = 0.0;
148 for t in 0..n_points {
149 let diff = data_ori[(j, t)] - data_ori[(k, t)];
150 sum += weights[t] * diff * diff;
151 }
152 (j, k, (-sum / h_sq).exp())
153 })
154 .collect::<Vec<_>>()
155 })
156 .collect();
157
158 let mut m1 = FdMatrix::zeros(nori, nori);
159 for j in 0..nori {
160 m1[(j, j)] = 1.0;
161 }
162 for (j, k, kval) in m1_upper {
163 m1[(j, k)] = kval;
164 m1[(k, j)] = kval;
165 }
166
167 let nori_f64 = nori as f64;
168
169 iter_maybe_parallel!(0..nobj)
170 .map(|i| {
171 let m2: Vec<f64> = (0..nori)
172 .map(|j| {
173 let mut sum = 0.0;
174 for t in 0..n_points {
175 let diff = data_obj[(i, t)] - data_ori[(j, t)];
176 sum += weights[t] * diff * diff;
177 }
178 (-sum / h_sq).exp()
179 })
180 .collect();
181
182 let (total_sum, valid_count) = kfsd_accumulate(&m2, &m1, nori);
183
184 if valid_count > 0 && total_sum >= 0.0 {
185 1.0 - total_sum.sqrt() / nori_f64
186 } else if total_sum < 0.0 {
187 1.0
188 } else {
189 0.0
190 }
191 })
192 .collect()
193}
194
195#[must_use = "expensive computation whose result should not be discarded"]
199pub fn kernel_functional_spatial_1d(
200 data_obj: &FdMatrix,
201 data_ori: &FdMatrix,
202 argvals: &[f64],
203 h: f64,
204) -> Vec<f64> {
205 let nobj = data_obj.nrows();
206 let nori = data_ori.nrows();
207 let n_points = data_obj.ncols();
208
209 if nobj == 0 || nori == 0 || n_points == 0 {
210 return Vec::new();
211 }
212
213 let weights = simpsons_weights(argvals);
214 kfsd_weighted(data_obj, data_ori, h, &weights)
215}
216
217#[must_use = "expensive computation whose result should not be discarded"]
219pub fn kernel_functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> Vec<f64> {
220 let nobj = data_obj.nrows();
221 let nori = data_ori.nrows();
222 let n_points = data_obj.ncols();
223
224 if nobj == 0 || nori == 0 || n_points == 0 {
225 return Vec::new();
226 }
227
228 let weights = vec![1.0; n_points];
229 kfsd_weighted(data_obj, data_ori, h, &weights)
230}