use super::functions::*;
pub struct GrowthFunction {
pub vc_dim: usize,
}
impl GrowthFunction {
pub fn new(vc_dim: usize) -> Self {
Self { vc_dim }
}
pub fn evaluate(&self, m: usize) -> usize {
VCDimension::new(self.vc_dim).sauer_shelah_bound(m)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CrossValidation {
pub n_folds: usize,
pub n_samples: usize,
pub shuffle: bool,
pub stratified: bool,
}
#[allow(dead_code)]
impl CrossValidation {
pub fn new(k: usize, n: usize) -> Self {
CrossValidation {
n_folds: k,
n_samples: n,
shuffle: true,
stratified: false,
}
}
pub fn k_fold_5(n: usize) -> Self {
CrossValidation::new(5, n)
}
pub fn loocv(n: usize) -> Self {
CrossValidation::new(n, n)
}
pub fn fold_size(&self) -> usize {
(self.n_samples + self.n_folds - 1) / self.n_folds
}
pub fn train_size(&self) -> usize {
self.n_samples - self.fold_size()
}
pub fn n_train_test_splits(&self) -> usize {
self.n_folds
}
}
pub struct EarlyStoppingReg {
pub max_iters: usize,
pub step_size: f64,
}
impl EarlyStoppingReg {
pub fn new(max_iters: usize, step_size: f64) -> Self {
Self {
max_iters,
step_size,
}
}
pub fn effective_lambda(&self) -> f64 {
1.0 / (self.step_size * self.max_iters as f64)
}
}
pub struct AdaBoost {
pub rounds: usize,
pub alphas: Vec<f64>,
pub weak_accuracies: Vec<f64>,
}
impl AdaBoost {
pub fn new(rounds: usize) -> Self {
Self {
rounds,
alphas: Vec::new(),
weak_accuracies: Vec::new(),
}
}
pub fn compute_alpha(weak_error: f64) -> f64 {
0.5 * ((1.0 - weak_error) / weak_error).ln()
}
pub fn training_error_bound(gammas: &[f64]) -> f64 {
let sum_gamma_sq: f64 = gammas.iter().map(|g| g * g).sum();
(-2.0 * sum_gamma_sq).exp()
}
pub fn add_round(&mut self, weak_accuracy: f64) {
let weak_error = 1.0 - weak_accuracy;
let alpha = Self::compute_alpha(weak_error);
self.alphas.push(alpha);
self.weak_accuracies.push(weak_accuracy);
}
}
pub struct OnlineGradientDescent {
pub weights: Vec<f64>,
pub eta: f64,
pub d: f64,
pub g: f64,
pub t: usize,
}
impl OnlineGradientDescent {
pub fn new(dim: usize, eta: f64, d: f64, g: f64) -> Self {
Self {
weights: vec![0.0; dim],
eta,
d,
g,
t: 0,
}
}
pub fn update(&mut self, gradient: &[f64]) {
for (wi, &gi) in self.weights.iter_mut().zip(gradient.iter()) {
*wi -= self.eta * gi;
}
let norm: f64 = self.weights.iter().map(|wi| wi * wi).sum::<f64>().sqrt();
if norm > self.d {
let scale = self.d / norm;
for wi in self.weights.iter_mut() {
*wi *= scale;
}
}
self.t += 1;
}
pub fn regret_bound(&self) -> f64 {
self.d * self.g * (2.0 * self.t as f64).sqrt()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SVMClassifier {
pub kernel_type: SVMKernel,
pub c_regularization: f64,
pub n_support_vectors: usize,
}
#[allow(dead_code)]
impl SVMClassifier {
pub fn new(kernel: SVMKernel, c: f64) -> Self {
SVMClassifier {
kernel_type: kernel,
c_regularization: c,
n_support_vectors: 0,
}
}
pub fn linear(c: f64) -> Self {
SVMClassifier::new(SVMKernel::Linear, c)
}
pub fn rbf(gamma: f64, c: f64) -> Self {
SVMClassifier::new(SVMKernel::RBF(gamma), c)
}
pub fn kernel_value(&self, x: &[f64], xp: &[f64]) -> f64 {
match &self.kernel_type {
SVMKernel::Linear => dot(x, xp),
SVMKernel::Polynomial(d) => (dot(x, xp) + 1.0).powi(*d as i32),
SVMKernel::RBF(gamma) => {
let sq_dist = x
.iter()
.zip(xp.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>();
(-gamma * sq_dist).exp()
}
SVMKernel::Sigmoid => (dot(x, xp)).tanh(),
}
}
pub fn sparsity_ratio(&self, n_training: usize) -> f64 {
if n_training == 0 {
return 0.0;
}
self.n_support_vectors as f64 / n_training as f64
}
}
pub struct FisherInformation {
pub theta: f64,
}
impl FisherInformation {
pub fn new(theta: f64) -> Self {
Self { theta }
}
pub fn estimate(log_density: impl Fn(f64, f64) -> f64, theta: f64, samples: &[f64]) -> f64 {
let h = 1e-5;
let n = samples.len() as f64;
samples
.iter()
.map(|&x| {
let score = (log_density(x, theta + h) - log_density(x, theta - h)) / (2.0 * h);
score * score
})
.sum::<f64>()
/ n
}
pub fn cramer_rao_bound(&self, fisher_val: f64) -> f64 {
if fisher_val > 0.0 {
1.0 / fisher_val
} else {
f64::INFINITY
}
}
}
pub struct RademacherComplexity {
pub n: usize,
pub class_size: usize,
}
impl RademacherComplexity {
pub fn new(n: usize, class_size: usize) -> Self {
Self { n, class_size }
}
pub fn upper_bound(&self) -> f64 {
(2.0 * (self.class_size as f64).ln() / self.n as f64).sqrt()
}
pub fn generalization_bound(&self, empirical_loss: f64, delta: f64) -> f64 {
let rn = self.upper_bound();
let confidence_term = ((2.0 / delta).ln() / (2.0 * self.n as f64)).sqrt();
empirical_loss + 2.0 * rn + confidence_term
}
}
pub struct UniformConvergence {
pub eps: f64,
pub delta: f64,
}
impl UniformConvergence {
pub fn new(eps: f64, delta: f64) -> Self {
Self { eps, delta }
}
pub fn required_samples(&self, class_size: usize) -> usize {
let log_h = (class_size as f64).ln();
let log_delta = (1.0 / self.delta).ln();
((2.0 * (log_h + log_delta)) / (self.eps * self.eps)).ceil() as usize
}
}
pub struct BiasVarianceTradeoff {
pub bias_squared: f64,
pub variance: f64,
pub noise: f64,
}
impl BiasVarianceTradeoff {
pub fn new(bias_squared: f64, variance: f64, noise: f64) -> Self {
Self {
bias_squared,
variance,
noise,
}
}
pub fn total_mse(&self) -> f64 {
self.bias_squared + self.variance + self.noise
}
}
pub struct MutualInformation;
impl MutualInformation {
pub fn compute(joint: &[Vec<f64>]) -> f64 {
if joint.is_empty() {
return 0.0;
}
let _n_rows = joint.len();
let n_cols = joint[0].len();
let px: Vec<f64> = joint.iter().map(|row| row.iter().sum::<f64>()).collect();
let py: Vec<f64> = (0..n_cols)
.map(|j| {
joint
.iter()
.map(|row| row.get(j).copied().unwrap_or(0.0))
.sum()
})
.collect();
let h_x: f64 = px.iter().filter(|&&p| p > 0.0).map(|&p| -p * p.ln()).sum();
let h_y: f64 = py.iter().filter(|&&p| p > 0.0).map(|&p| -p * p.ln()).sum();
let h_xy: f64 = joint
.iter()
.flat_map(|row| row.iter())
.filter(|&&p| p > 0.0)
.map(|&p| -p * p.ln())
.sum();
(h_x + h_y - h_xy).max(0.0)
}
pub fn data_processing_inequality(joint_xy: &[Vec<f64>], joint_xz: &[Vec<f64>]) -> bool {
let i_xy = Self::compute(joint_xy);
let i_xz = Self::compute(joint_xz);
i_xz <= i_xy + 1e-9
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GradientBoosting {
pub n_estimators: usize,
pub learning_rate: f64,
pub max_depth: usize,
pub loss: GBLoss,
}
#[allow(dead_code)]
impl GradientBoosting {
pub fn new(n: usize, lr: f64, depth: usize, loss: GBLoss) -> Self {
GradientBoosting {
n_estimators: n,
learning_rate: lr,
max_depth: depth,
loss,
}
}
pub fn xgboost_style(n: usize) -> Self {
GradientBoosting::new(n, 0.1, 6, GBLoss::MSE)
}
pub fn effective_shrinkage(&self) -> f64 {
self.learning_rate
}
pub fn n_leaves_upper_bound(&self) -> usize {
2usize.pow(self.max_depth as u32)
}
pub fn is_regularized(&self) -> bool {
self.learning_rate < 1.0
}
}
pub struct KernelMatrix {
pub entries: Vec<Vec<f64>>,
pub n: usize,
}
impl KernelMatrix {
pub fn compute(kernel: &KernelFunction, data: &[Vec<f64>]) -> Self {
let n = data.len();
let entries: Vec<Vec<f64>> = (0..n)
.map(|i| {
(0..n)
.map(|j| kernel.evaluate(&data[i], &data[j]))
.collect()
})
.collect();
Self { entries, n }
}
pub fn trace(&self) -> f64 {
(0..self.n).map(|i| self.entries[i][i]).sum()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum SVMKernel {
Linear,
Polynomial(u32),
RBF(f64),
Sigmoid,
}
pub struct KLDivergence;
impl KLDivergence {
pub fn compute(p: &[f64], q: &[f64]) -> f64 {
p.iter()
.zip(q.iter())
.filter(|(&pi, _)| pi > 0.0)
.map(|(&pi, &qi)| {
if qi == 0.0 {
f64::INFINITY
} else {
pi * (pi / qi).ln()
}
})
.sum()
}
pub fn is_nonneg(p: &[f64], q: &[f64]) -> bool {
Self::compute(p, q) >= -1e-9
}
}
pub struct VCDimension {
pub dim: usize,
}
impl VCDimension {
pub fn new(dim: usize) -> Self {
Self { dim }
}
pub fn sauer_shelah_bound(&self, m: usize) -> usize {
let d = self.dim;
let mut bound = 0usize;
let mut binom = 1usize;
for i in 0..=d.min(m) {
if i > 0 {
binom = binom
.saturating_mul(m - i + 1)
.checked_div(i)
.unwrap_or(binom);
}
bound = bound.saturating_add(binom);
}
bound
}
pub fn can_shatter(&self, m: usize) -> bool {
m <= self.dim
}
pub fn fundamental_theorem_pac(&self) -> bool {
self.dim < usize::MAX
}
}
pub enum KernelType {
Linear,
Polynomial { degree: u32, offset: f64 },
Rbf { sigma: f64 },
Laplace { sigma: f64 },
}
pub struct RegretBound {
pub t: usize,
pub d: f64,
pub g: f64,
}
impl RegretBound {
pub fn new(t: usize, d: f64, g: f64) -> Self {
Self { t, d, g }
}
pub fn ogd_bound(&self) -> f64 {
self.d * self.g * (2.0 * self.t as f64).sqrt()
}
}
pub struct SampleComplexity {
pub eps: f64,
pub delta: f64,
pub vc_dim: usize,
}
impl SampleComplexity {
pub fn new(eps: f64, delta: f64, vc_dim: usize) -> Self {
Self { eps, delta, vc_dim }
}
pub fn compute(&self) -> usize {
let d = self.vc_dim as f64;
let numerator = d * (d / self.eps + 1.0).ln() + (2.0 / self.delta).ln();
(numerator / self.eps).ceil() as usize
}
}
pub struct KernelSVMTrainer {
pub n: usize,
pub alphas: Vec<f64>,
pub labels: Vec<f64>,
pub bias: f64,
pub c: f64,
}
impl KernelSVMTrainer {
pub fn new(n: usize, labels: Vec<f64>, c: f64) -> Self {
Self {
n,
alphas: vec![0.0; n],
labels,
bias: 0.0,
c,
}
}
pub fn decision(&self, kernel_row: &[f64]) -> f64 {
self.alphas
.iter()
.zip(self.labels.iter())
.zip(kernel_row.iter())
.map(|((a, y), k)| a * y * k)
.sum::<f64>()
+ self.bias
}
pub fn smo_step(&mut self, i: usize, j: usize, k: &[Vec<f64>]) -> bool {
if i == j {
return false;
}
let yi = self.labels[i];
let yj = self.labels[j];
let fi = self.decision(&k[i]);
let fj = self.decision(&k[j]);
let ei = fi - yi;
let ej = fj - yj;
let eta = k[i][i] + k[j][j] - 2.0 * k[i][j];
if eta <= 0.0 {
return false;
}
let alpha_j_old = self.alphas[j];
let alpha_i_old = self.alphas[i];
let (l, h) = if (yi - yj).abs() < 1e-9 {
let sum = alpha_i_old + alpha_j_old;
((sum - self.c).max(0.0), sum.min(self.c))
} else {
let diff = alpha_j_old - alpha_i_old;
((-diff).max(0.0), (self.c - diff).min(self.c))
};
let alpha_j_new = (alpha_j_old + yj * (ei - ej) / eta).clamp(l, h);
if (alpha_j_new - alpha_j_old).abs() < 1e-5 {
return false;
}
let alpha_i_new = alpha_i_old + yi * yj * (alpha_j_old - alpha_j_new);
let b1 = self.bias
- ei
- yi * (alpha_i_new - alpha_i_old) * k[i][i]
- yj * (alpha_j_new - alpha_j_old) * k[i][j];
let b2 = self.bias
- ej
- yi * (alpha_i_new - alpha_i_old) * k[i][j]
- yj * (alpha_j_new - alpha_j_old) * k[j][j];
self.bias = (b1 + b2) / 2.0;
self.alphas[i] = alpha_i_new;
self.alphas[j] = alpha_j_new;
true
}
pub fn generalization_bound(radius: f64, margin: f64, n: usize) -> f64 {
(radius * radius) / (margin * margin * n as f64)
}
}
pub struct PACLearner {
pub eps: f64,
pub delta: f64,
}
impl PACLearner {
pub fn new(eps: f64, delta: f64) -> Self {
Self { eps, delta }
}
pub fn required_samples(&self, vc_dim: usize) -> usize {
SampleComplexity::new(self.eps, self.delta, vc_dim).compute()
}
}
pub struct ELBO {
pub kl_term: f64,
pub reconstruction_term: f64,
}
impl ELBO {
pub fn new(reconstruction_term: f64, kl_term: f64) -> Self {
Self {
kl_term,
reconstruction_term,
}
}
pub fn value(&self) -> f64 {
self.reconstruction_term - self.kl_term
}
pub fn compute(q: &[f64], p_joint: &[f64]) -> f64 {
q.iter()
.zip(p_joint.iter())
.filter(|(&qi, _)| qi > 0.0)
.map(|(&qi, &pi)| {
if pi == 0.0 {
f64::NEG_INFINITY
} else {
qi * (pi / qi).ln()
}
})
.sum()
}
}
pub struct TikhonovReg {
pub lambda: f64,
}
impl TikhonovReg {
pub fn new(lambda: f64) -> Self {
Self { lambda }
}
pub fn solve(&self, x: &[Vec<f64>], y: &[f64]) -> Vec<f64> {
let d = if x.is_empty() { 0 } else { x[0].len() };
let n = x.len();
let mut xtx = vec![vec![0.0f64; d]; d];
for i in 0..d {
xtx[i][i] = self.lambda;
}
for row in x {
for i in 0..d {
for j in 0..d {
xtx[i][j] += row[i] * row[j];
}
}
}
let mut xty = vec![0.0f64; d];
for (row, &yi) in x.iter().zip(y.iter()) {
for j in 0..d {
xty[j] += row[j] * yi;
}
}
gauss_solve(&xtx, &xty, d, n)
}
pub fn penalty(&self, w: &[f64]) -> f64 {
self.lambda * w.iter().map(|wi| wi * wi).sum::<f64>()
}
}
pub struct CausalBackdoor {
pub cond_probs: Vec<f64>,
pub stratum_probs: Vec<f64>,
}
impl CausalBackdoor {
pub fn new(cond_probs: Vec<f64>, stratum_probs: Vec<f64>) -> Self {
Self {
cond_probs,
stratum_probs,
}
}
pub fn adjust(&self) -> f64 {
self.cond_probs
.iter()
.zip(self.stratum_probs.iter())
.map(|(p, q)| p * q)
.sum()
}
pub fn confounding_bias(&self, observational: f64) -> f64 {
(observational - self.adjust()).abs()
}
pub fn is_valid(&self) -> bool {
let sum: f64 = self.stratum_probs.iter().sum();
(sum - 1.0).abs() < 1e-6
}
}
pub struct DoubleRademacher {
pub rademacher: RademacherComplexity,
}
impl DoubleRademacher {
pub fn new(n: usize, class_size: usize) -> Self {
Self {
rademacher: RademacherComplexity::new(n, class_size),
}
}
pub fn two_sided_bound(&self) -> f64 {
2.0 * self.rademacher.upper_bound()
}
}
pub struct Perceptron {
pub weights: Vec<f64>,
pub bias: f64,
pub mistakes: usize,
}
impl Perceptron {
pub fn new(dim: usize) -> Self {
Self {
weights: vec![0.0; dim],
bias: 0.0,
mistakes: 0,
}
}
pub fn predict(&self, x: &[f64]) -> f64 {
let score = dot(&self.weights, x) + self.bias;
if score >= 0.0 {
1.0
} else {
-1.0
}
}
pub fn update(&mut self, x: &[f64], label: f64) -> bool {
let pred = self.predict(x);
if (pred * label) <= 0.0 {
for (wi, &xi) in self.weights.iter_mut().zip(x.iter()) {
*wi += label * xi;
}
self.bias += label;
self.mistakes += 1;
true
} else {
false
}
}
pub fn mistake_bound(radius: f64, margin: f64) -> usize {
((radius / margin).powi(2)).ceil() as usize
}
}
pub struct GaussianComplexity {
pub n: usize,
pub class_size: usize,
}
impl GaussianComplexity {
pub fn new(n: usize, class_size: usize) -> Self {
Self { n, class_size }
}
pub fn upper_bound(&self) -> f64 {
(2.0 * (self.class_size as f64).ln() / self.n as f64).sqrt()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum GBLoss {
MSE,
MAE,
LogLoss,
Huber(f64),
}
pub struct LassoReg {
pub lambda: f64,
}
impl LassoReg {
pub fn new(lambda: f64) -> Self {
Self { lambda }
}
pub fn soft_threshold(&self, w: &[f64]) -> Vec<f64> {
w.iter()
.map(|&wi| {
let abs_wi = wi.abs();
if abs_wi <= self.lambda {
0.0
} else {
wi.signum() * (abs_wi - self.lambda)
}
})
.collect()
}
pub fn penalty(&self, w: &[f64]) -> f64 {
self.lambda * w.iter().map(|wi| wi.abs()).sum::<f64>()
}
}
pub struct UCBBandit {
pub n: usize,
pub counts: Vec<usize>,
pub means: Vec<f64>,
pub t: usize,
}
impl UCBBandit {
pub fn new(n: usize) -> Self {
Self {
n,
counts: vec![0; n],
means: vec![0.0; n],
t: 0,
}
}
pub fn select(&self) -> usize {
if let Some(i) = self.counts.iter().position(|&c| c == 0) {
return i;
}
let ln_t = (self.t as f64).ln();
(0..self.n)
.max_by(|&i, &j| {
let ucb_i = self.means[i] + (2.0 * ln_t / self.counts[i] as f64).sqrt();
let ucb_j = self.means[j] + (2.0 * ln_t / self.counts[j] as f64).sqrt();
ucb_i
.partial_cmp(&ucb_j)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(0)
}
pub fn update(&mut self, arm: usize, reward: f64) {
self.counts[arm] += 1;
let n = self.counts[arm] as f64;
self.means[arm] += (reward - self.means[arm]) / n;
self.t += 1;
}
pub fn regret_bound_upper(&self) -> f64 {
let t = self.t as f64;
let n = self.n as f64;
(n * t * t.ln()).sqrt()
}
}
pub struct PACBayesGeneralization {
pub kl_qp: f64,
pub n: usize,
pub delta: f64,
}
impl PACBayesGeneralization {
pub fn new(kl_qp: f64, n: usize, delta: f64) -> Self {
Self { kl_qp, n, delta }
}
pub fn mcallester_bound(&self, empirical_loss: f64) -> f64 {
let penalty =
((self.kl_qp + (self.n as f64 / self.delta).ln()) / (2.0 * self.n as f64)).sqrt();
empirical_loss + penalty
}
pub fn catoni_bound(&self, empirical_loss: f64, lambda: f64) -> f64 {
let scale = 1.0 / (1.0 - lambda / 2.0);
let penalty = self.kl_qp / (2.0 * lambda * self.n as f64);
scale * (empirical_loss + penalty)
}
pub fn optimal_lambda(&self, empirical_loss: f64) -> f64 {
let ratio = self.n as f64 * empirical_loss / (self.kl_qp.max(1e-9));
1.0 / (1.0 + ratio).sqrt()
}
}
pub struct ExponentialWeightsAlgorithm {
pub n: usize,
pub eta: f64,
pub weights: Vec<f64>,
pub rounds: usize,
}
impl ExponentialWeightsAlgorithm {
pub fn new(n: usize, eta: f64) -> Self {
Self {
n,
eta,
weights: vec![1.0; n],
rounds: 0,
}
}
pub fn distribution(&self) -> Vec<f64> {
let total: f64 = self.weights.iter().sum();
self.weights.iter().map(|w| w / total).collect()
}
pub fn update(&mut self, losses: &[f64]) {
for (w, &l) in self.weights.iter_mut().zip(losses.iter()) {
*w *= (-self.eta * l).exp();
}
self.rounds += 1;
}
pub fn regret_bound(&self) -> f64 {
let t = self.rounds as f64;
(self.n as f64).ln() / self.eta + self.eta * t / 2.0
}
pub fn optimal_eta(n: usize, t: usize) -> f64 {
(2.0 * (n as f64).ln() / t as f64).sqrt()
}
}
pub struct FeatureMap {
pub feature_dim: usize,
}
impl FeatureMap {
pub fn new(feature_dim: usize) -> Self {
Self { feature_dim }
}
pub fn inner_product(&self, x: &[f64], y: &[f64]) -> f64 {
dot(x, y)
}
}
pub struct KernelSVM {
pub alphas: Vec<f64>,
pub labels: Vec<f64>,
pub bias: f64,
pub c: f64,
}
impl KernelSVM {
pub fn new(n: usize, c: f64) -> Self {
Self {
alphas: vec![0.0; n],
labels: vec![1.0; n],
bias: 0.0,
c,
}
}
pub fn predict(&self, kernel_vals: &[f64]) -> f64 {
let sum: f64 = self
.alphas
.iter()
.zip(self.labels.iter())
.zip(kernel_vals.iter())
.map(|((a, y), k)| a * y * k)
.sum();
sum + self.bias
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GaussianProcess {
pub mean: f64,
pub length_scale: f64,
pub signal_variance: f64,
pub noise_variance: f64,
}
#[allow(dead_code)]
impl GaussianProcess {
pub fn new(mean: f64, length_scale: f64, signal_var: f64, noise_var: f64) -> Self {
GaussianProcess {
mean,
length_scale,
signal_variance: signal_var,
noise_variance: noise_var,
}
}
pub fn default_rbf() -> Self {
GaussianProcess::new(0.0, 1.0, 1.0, 0.01)
}
pub fn rbf_kernel(&self, x: f64, xp: f64) -> f64 {
let d = x - xp;
self.signal_variance * (-d * d / (2.0 * self.length_scale.powi(2))).exp()
}
pub fn predictive_variance(&self, x: f64, train_x: &[f64]) -> f64 {
let k_star_star = self.rbf_kernel(x, x);
let k_noise: Vec<f64> = train_x.iter().map(|&xi| self.rbf_kernel(x, xi)).collect();
let contrib: f64 = k_noise.iter().map(|&k| k * k).sum::<f64>()
/ (self.signal_variance + self.noise_variance).max(1e-10);
(k_star_star - contrib).max(self.noise_variance)
}
pub fn log_marginal_likelihood_approx(&self, n: usize) -> f64 {
-(n as f64) / 2.0 * (2.0 * std::f64::consts::PI * self.signal_variance).ln()
}
}
pub struct KernelFunction {
pub kernel_type: KernelType,
}
impl KernelFunction {
pub fn new(kernel_type: KernelType) -> Self {
Self { kernel_type }
}
pub fn evaluate(&self, x: &[f64], y: &[f64]) -> f64 {
match &self.kernel_type {
KernelType::Linear => dot(x, y),
KernelType::Polynomial { degree, offset } => (dot(x, y) + offset).powi(*degree as i32),
KernelType::Rbf { sigma } => {
let diff_sq: f64 = x.iter().zip(y).map(|(a, b)| (a - b).powi(2)).sum();
(-diff_sq / (2.0 * sigma * sigma)).exp()
}
KernelType::Laplace { sigma } => {
let diff_norm: f64 = x.iter().zip(y).map(|(a, b)| (a - b).abs()).sum();
(-diff_norm / sigma).exp()
}
}
}
pub fn is_positive_definite(&self, points: &[Vec<f64>]) -> bool {
let n = points.len();
let mut k: Vec<Vec<f64>> = (0..n)
.map(|i| {
(0..n)
.map(|j| self.evaluate(&points[i], &points[j]))
.collect()
})
.collect();
for i in 0..n {
for j in 0..i {
let mut sum = k[i][j];
for l in 0..j {
sum -= k[i][l] * k[j][l];
}
k[i][j] = sum / k[j][j];
}
let mut diag = k[i][i];
for l in 0..i {
diag -= k[i][l] * k[i][l];
}
if diag < -1e-9 {
return false;
}
k[i][i] = diag.max(0.0).sqrt();
}
true
}
}