1use super::helpers::*;
4use crate::matrix::FdMatrix;
5use crate::scalar_on_function::{sigmoid, FregreLmResult, FunctionalLogisticResult};
6use rand::prelude::*;
7
8pub struct SobolIndicesResult {
14 pub first_order: Vec<f64>,
16 pub total_order: Vec<f64>,
18 pub var_y: f64,
20 pub component_variance: Vec<f64>,
22}
23
24pub fn sobol_indices(
28 fit: &FregreLmResult,
29 data: &FdMatrix,
30 y: &[f64],
31 scalar_covariates: Option<&FdMatrix>,
32) -> Option<SobolIndicesResult> {
33 let (n, m) = data.shape();
34 if n < 2 || n != y.len() || m != fit.fpca.mean.len() {
35 return None;
36 }
37 let _ = scalar_covariates; let ncomp = fit.ncomp;
39 if ncomp == 0 {
40 return None;
41 }
42
43 let score_var = compute_score_variance(&fit.fpca.scores, n, ncomp);
44
45 let component_variance: Vec<f64> = (0..ncomp)
46 .map(|k| fit.coefficients[1 + k].powi(2) * score_var[k])
47 .collect();
48
49 let y_mean: f64 = y.iter().sum::<f64>() / n as f64;
50 let var_y: f64 = y.iter().map(|&yi| (yi - y_mean).powi(2)).sum::<f64>() / (n - 1) as f64;
51 if var_y == 0.0 {
52 return None;
53 }
54
55 let first_order: Vec<f64> = component_variance.iter().map(|&cv| cv / var_y).collect();
56 let total_order = first_order.clone(); Some(SobolIndicesResult {
59 first_order,
60 total_order,
61 var_y,
62 component_variance,
63 })
64}
65
66pub fn sobol_indices_logistic(
68 fit: &FunctionalLogisticResult,
69 data: &FdMatrix,
70 scalar_covariates: Option<&FdMatrix>,
71 n_samples: usize,
72 seed: u64,
73) -> Option<SobolIndicesResult> {
74 let (n, m) = data.shape();
75 if n < 2 || m != fit.fpca.mean.len() || n_samples == 0 {
76 return None;
77 }
78 let ncomp = fit.ncomp;
79 if ncomp == 0 {
80 return None;
81 }
82 let p_scalar = fit.gamma.len();
83 let scores = project_scores(data, &fit.fpca.mean, &fit.fpca.rotation, ncomp);
84 let mean_z = compute_mean_scalar(scalar_covariates, p_scalar, n);
85
86 let eval_model = |s: &[f64]| -> f64 {
87 let mut eta = fit.intercept;
88 for k in 0..ncomp {
89 eta += fit.coefficients[1 + k] * s[k];
90 }
91 for j in 0..p_scalar {
92 eta += fit.gamma[j] * mean_z[j];
93 }
94 sigmoid(eta)
95 };
96
97 let mut rng = StdRng::seed_from_u64(seed);
98 let (mat_a, mat_b) = generate_sobol_matrices(&scores, n, ncomp, n_samples, &mut rng);
99
100 let f_a: Vec<f64> = mat_a.iter().map(|s| eval_model(s)).collect();
101 let f_b: Vec<f64> = mat_b.iter().map(|s| eval_model(s)).collect();
102
103 let mean_fa = f_a.iter().sum::<f64>() / n_samples as f64;
104 let var_fa = f_a.iter().map(|&v| (v - mean_fa).powi(2)).sum::<f64>() / n_samples as f64;
105
106 if var_fa < 1e-15 {
107 return None;
108 }
109
110 let mut first_order = vec![0.0; ncomp];
111 let mut total_order = vec![0.0; ncomp];
112 let mut component_variance = vec![0.0; ncomp];
113
114 for k in 0..ncomp {
115 let (s_k, st_k) = compute_sobol_component(
116 &mat_a,
117 &mat_b,
118 &f_a,
119 &f_b,
120 var_fa,
121 k,
122 n_samples,
123 &eval_model,
124 );
125 first_order[k] = s_k;
126 total_order[k] = st_k;
127 component_variance[k] = s_k * var_fa;
128 }
129
130 Some(SobolIndicesResult {
131 first_order,
132 total_order,
133 var_y: var_fa,
134 component_variance,
135 })
136}
137
138pub struct FunctionalSaliencyResult {
144 pub saliency_map: FdMatrix,
146 pub mean_absolute_saliency: Vec<f64>,
148}
149
150pub fn functional_saliency(
154 fit: &FregreLmResult,
155 data: &FdMatrix,
156 scalar_covariates: Option<&FdMatrix>,
157) -> Option<FunctionalSaliencyResult> {
158 let (n, m) = data.shape();
159 if n == 0 || m != fit.fpca.mean.len() {
160 return None;
161 }
162 let _ = scalar_covariates;
163 let ncomp = fit.ncomp;
164 if ncomp == 0 {
165 return None;
166 }
167 let scores = project_scores(data, &fit.fpca.mean, &fit.fpca.rotation, ncomp);
168 let mean_scores = compute_column_means(&scores, ncomp);
169
170 let weights: Vec<f64> = (0..ncomp).map(|k| fit.coefficients[1 + k]).collect();
171 let saliency_map = compute_saliency_map(
172 &scores,
173 &mean_scores,
174 &weights,
175 &fit.fpca.rotation,
176 n,
177 m,
178 ncomp,
179 );
180 let mean_absolute_saliency = mean_absolute_column(&saliency_map, n, m);
181
182 Some(FunctionalSaliencyResult {
183 saliency_map,
184 mean_absolute_saliency,
185 })
186}
187
188pub fn functional_saliency_logistic(
190 fit: &FunctionalLogisticResult,
191) -> Option<FunctionalSaliencyResult> {
192 let m = fit.beta_t.len();
193 let n = fit.probabilities.len();
194 if n == 0 || m == 0 {
195 return None;
196 }
197
198 let mut saliency_map = FdMatrix::zeros(n, m);
200 for i in 0..n {
201 let pi = fit.probabilities[i];
202 let w = pi * (1.0 - pi);
203 for j in 0..m {
204 saliency_map[(i, j)] = w * fit.beta_t[j];
205 }
206 }
207
208 let mut mean_absolute_saliency = vec![0.0; m];
209 for j in 0..m {
210 for i in 0..n {
211 mean_absolute_saliency[j] += saliency_map[(i, j)].abs();
212 }
213 mean_absolute_saliency[j] /= n as f64;
214 }
215
216 Some(FunctionalSaliencyResult {
217 saliency_map,
218 mean_absolute_saliency,
219 })
220}
221
222pub struct ImportantInterval {
228 pub start_idx: usize,
230 pub end_idx: usize,
232 pub importance: f64,
234}
235
236pub struct DomainSelectionResult {
238 pub pointwise_importance: Vec<f64>,
240 pub intervals: Vec<ImportantInterval>,
242 pub window_width: usize,
244 pub threshold: f64,
246}
247
248pub fn domain_selection(
250 fit: &FregreLmResult,
251 window_width: usize,
252 threshold: f64,
253) -> Option<DomainSelectionResult> {
254 compute_domain_selection(&fit.beta_t, window_width, threshold)
255}
256
257pub fn domain_selection_logistic(
259 fit: &FunctionalLogisticResult,
260 window_width: usize,
261 threshold: f64,
262) -> Option<DomainSelectionResult> {
263 compute_domain_selection(&fit.beta_t, window_width, threshold)
264}