pub mod classification;
pub mod drift;
pub mod evaluator;
pub mod ranking;
use crate::primitives::{Matrix, Vector};
#[must_use]
#[provable_contracts_macros::contract("metrics-regression-v1", equation = "r_squared")]
pub fn r_squared(y_pred: &Vector<f32>, y_true: &Vector<f32>) -> f32 {
contract_pre_r_squared!(y_pred.as_slice());
assert_eq!(y_pred.len(), y_true.len(), "Vectors must have same length");
let y_mean = y_true.mean();
let ss_res: f32 = y_true
.as_slice()
.iter()
.zip(y_pred.as_slice().iter())
.map(|(t, p)| (t - p).powi(2))
.sum();
let ss_tot: f32 = y_true.as_slice().iter().map(|t| (t - y_mean).powi(2)).sum();
if ss_tot == 0.0 {
return 0.0;
}
1.0 - (ss_res / ss_tot)
}
#[must_use]
#[provable_contracts_macros::contract("metrics-regression-v1", equation = "mse")]
pub fn mse(y_pred: &Vector<f32>, y_true: &Vector<f32>) -> f32 {
contract_pre_mse!(y_pred.as_slice());
assert_eq!(y_pred.len(), y_true.len(), "Vectors must have same length");
assert!(!y_true.is_empty(), "Vectors cannot be empty");
let n = y_true.len() as f32;
let sum_sq_error: f32 = y_true
.as_slice()
.iter()
.zip(y_pred.as_slice().iter())
.map(|(t, p)| (t - p).powi(2))
.sum();
sum_sq_error / n
}
#[must_use]
#[provable_contracts_macros::contract("metrics-regression-v1", equation = "mae")]
pub fn mae(y_pred: &Vector<f32>, y_true: &Vector<f32>) -> f32 {
contract_pre_mae!(y_pred.as_slice());
assert_eq!(y_pred.len(), y_true.len(), "Vectors must have same length");
assert!(!y_true.is_empty(), "Vectors cannot be empty");
let n = y_true.len() as f32;
let sum_abs_error: f32 = y_true
.as_slice()
.iter()
.zip(y_pred.as_slice().iter())
.map(|(t, p)| (t - p).abs())
.sum();
sum_abs_error / n
}
#[must_use]
#[provable_contracts_macros::contract("metrics-regression-v1", equation = "rmse")]
pub fn rmse(y_pred: &Vector<f32>, y_true: &Vector<f32>) -> f32 {
contract_pre_rmse!(y_pred.as_slice());
mse(y_pred, y_true).sqrt()
}
#[must_use]
#[provable_contracts_macros::contract("metrics-clustering-v1", equation = "inertia")]
pub fn inertia(data: &Matrix<f32>, centroids: &Matrix<f32>, labels: &[usize]) -> f32 {
contract_pre_inertia!();
let mut total = 0.0;
for (i, &label) in labels.iter().enumerate() {
let point = data.row(i);
let centroid = centroids.row(label);
let diff = &point - ¢roid;
total += diff.norm_squared();
}
total
}
fn mean_intra_cluster_distance(
data: &Matrix<f32>,
point_idx: usize,
cluster: usize,
labels: &[usize],
) -> f32 {
let point = data.row(point_idx);
let distances: Vec<f32> = labels
.iter()
.enumerate()
.filter(|&(j, &label)| j != point_idx && label == cluster)
.map(|(j, _)| {
let other = data.row(j);
(&point - &other).norm()
})
.collect();
if distances.is_empty() {
0.0
} else {
distances.iter().sum::<f32>() / distances.len() as f32
}
}
fn min_inter_cluster_distance(
data: &Matrix<f32>,
point_idx: usize,
cluster: usize,
labels: &[usize],
n_clusters: usize,
) -> f32 {
let point = data.row(point_idx);
let mut min_mean = f32::INFINITY;
for other_cluster in 0..n_clusters {
if other_cluster == cluster {
continue;
}
let distances: Vec<f32> = labels
.iter()
.enumerate()
.filter(|&(_, &label)| label == other_cluster)
.map(|(j, _)| {
let other = data.row(j);
(&point - &other).norm()
})
.collect();
if !distances.is_empty() {
let mean_dist = distances.iter().sum::<f32>() / distances.len() as f32;
min_mean = min_mean.min(mean_dist);
}
}
if min_mean == f32::INFINITY {
0.0
} else {
min_mean
}
}
fn silhouette_coefficient(a_i: f32, b_i: f32) -> f32 {
contract_pre_silhouette_coefficient!();
let max_ab = a_i.max(b_i);
if max_ab == 0.0 {
0.0
} else {
(b_i - a_i) / max_ab
}
}
#[must_use]
#[provable_contracts_macros::contract("metrics-clustering-v1", equation = "silhouette_score")]
pub fn silhouette_score(data: &Matrix<f32>, labels: &[usize]) -> f32 {
contract_pre_silhouette_score!();
let n_samples = data.n_rows();
if n_samples < 2 {
return 0.0;
}
let n_clusters = labels.iter().max().map_or(0, |&m| m + 1);
if n_clusters < 2 {
return 0.0;
}
let silhouettes: Vec<f32> = (0..n_samples)
.map(|i| {
let cluster = labels[i];
let a_i = mean_intra_cluster_distance(data, i, cluster, labels);
let b_i = min_inter_cluster_distance(data, i, cluster, labels, n_clusters);
silhouette_coefficient(a_i, b_i)
})
.collect();
silhouettes.iter().sum::<f32>() / silhouettes.len() as f32
}
#[cfg(test)]
#[path = "metrics_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "tests_regression_contract.rs"]
mod tests_regression_contract;
#[cfg(test)]
#[path = "tests_clustering_contract.rs"]
mod tests_clustering_contract;
#[cfg(test)]
#[path = "tests_ranking_contract.rs"]
mod tests_ranking_contract;