use crate::error::FdarError;
use crate::explain::{compute_lime, LimeResult};
use crate::matrix::FdMatrix;
use super::FpcPredictor;
#[must_use = "expensive computation whose result should not be discarded"]
pub fn generic_lime(
model: &dyn FpcPredictor,
data: &FdMatrix,
_scalar_covariates: Option<&FdMatrix>,
observation: usize,
n_samples: usize,
kernel_width: f64,
seed: u64,
) -> Result<LimeResult, FdarError> {
let (n, m) = data.shape();
if observation >= n {
return Err(FdarError::InvalidParameter {
parameter: "observation",
message: format!("observation {observation} >= n {n}"),
});
}
if m != model.fpca_mean().len() {
return Err(FdarError::InvalidDimension {
parameter: "data columns",
expected: model.fpca_mean().len().to_string(),
actual: m.to_string(),
});
}
if n_samples == 0 {
return Err(FdarError::InvalidParameter {
parameter: "n_samples",
message: "n_samples must be > 0".into(),
});
}
if kernel_width <= 0.0 {
return Err(FdarError::InvalidParameter {
parameter: "kernel_width",
message: format!("kernel_width must be > 0, got {kernel_width}"),
});
}
let ncomp = model.ncomp();
if ncomp == 0 {
return Err(FdarError::InvalidParameter {
parameter: "ncomp",
message: "model has 0 components".into(),
});
}
let scores = model.project(data);
let obs_scores: Vec<f64> = (0..ncomp).map(|k| scores[(observation, k)]).collect();
let mut score_sd = vec![0.0; ncomp];
for k in 0..ncomp {
let mut ss = 0.0;
for i in 0..n {
let s = scores[(i, k)];
ss += s * s;
}
score_sd[k] = (ss / (n - 1).max(1) as f64).sqrt().max(1e-10);
}
let predict = |s: &[f64]| -> f64 { model.predict_from_scores(s, None) };
compute_lime(
&obs_scores,
&score_sd,
ncomp,
n_samples,
kernel_width,
seed,
observation,
&predict,
)
.ok_or_else(|| FdarError::ComputationFailed {
operation: "generic_lime",
detail: "LIME computation failed; the perturbation neighborhood may be too narrow — try increasing kernel_width or n_samples".into(),
})
}