use super::functions::*;
use super::functions::{ConvexFunction, ProxableFunction};
#[derive(Debug, Clone)]
pub struct GradientDescent {
pub learning_rate: f64,
pub max_iter: usize,
pub tol: f64,
}
impl GradientDescent {
pub fn new(lr: f64, max_iter: usize, tol: f64) -> Self {
Self {
learning_rate: lr,
max_iter,
tol,
}
}
pub fn backtracking_line_search<F: ConvexFunction>(f: &F, x: &[f64], grad: &[f64]) -> f64 {
let c1 = 1e-4_f64;
let rho = 0.5_f64;
let mut alpha = 1.0_f64;
let fx = f.eval(x);
let grad_norm_sq: f64 = grad.iter().map(|g| g * g).sum();
for _ in 0..50 {
let x_new: Vec<f64> = x.iter().zip(grad).map(|(xi, gi)| xi - alpha * gi).collect();
if f.eval(&x_new) <= fx - c1 * alpha * grad_norm_sq {
break;
}
alpha *= rho;
}
alpha
}
pub fn minimize<F: ConvexFunction>(&self, f: &F, x0: &[f64]) -> (Vec<f64>, f64, usize) {
let mut x = x0.to_vec();
let mut iters = 0_usize;
for k in 0..self.max_iter {
let grad = f.gradient(&x);
let grad_norm: f64 = grad.iter().map(|g| g * g).sum::<f64>().sqrt();
if grad_norm < self.tol {
iters = k;
break;
}
let alpha = Self::backtracking_line_search(f, &x, &grad);
for (xi, gi) in x.iter_mut().zip(&grad) {
*xi -= alpha * gi;
}
iters = k + 1;
}
let fval = f.eval(&x);
(x, fval, iters)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct MirrorDescentSolver {
pub eta: f64,
pub max_iter: usize,
pub tol: f64,
pub use_entropy: bool,
}
impl MirrorDescentSolver {
pub fn new(eta: f64, max_iter: usize, tol: f64, use_entropy: bool) -> Self {
Self {
eta,
max_iter,
tol,
use_entropy,
}
}
pub fn project_simplex(v: &[f64]) -> Vec<f64> {
let _n = v.len();
let mut u: Vec<f64> = v.to_vec();
u.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let mut cssv = 0.0_f64;
let mut rho = 0_usize;
for (j, &uj) in u.iter().enumerate() {
cssv += uj;
if uj - (cssv - 1.0) / (j as f64 + 1.0) > 0.0 {
rho = j;
}
}
let cssv_rho: f64 = u[..=rho].iter().sum();
let theta = (cssv_rho - 1.0) / (rho as f64 + 1.0);
v.iter().map(|vi| (vi - theta).max(0.0)).collect()
}
fn entropy_step(x: &[f64], grad: &[f64], eta: f64) -> Vec<f64> {
let log_x_new: Vec<f64> = x
.iter()
.zip(grad)
.map(|(xi, gi)| xi.ln() - eta * gi)
.collect();
let max_log = log_x_new.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let exp_vals: Vec<f64> = log_x_new.iter().map(|v| (v - max_log).exp()).collect();
let z: f64 = exp_vals.iter().sum();
exp_vals.iter().map(|v| v / z).collect()
}
pub fn minimize<F: ConvexFunction>(&self, f: &F, x0: &[f64]) -> (Vec<f64>, f64, usize) {
let mut x = if self.use_entropy {
Self::project_simplex(x0)
} else {
x0.to_vec()
};
let mut best_x = x.clone();
let mut best_f = f.eval(&x);
let mut iters = self.max_iter;
for k in 0..self.max_iter {
let grad = f.gradient(&x);
let grad_norm: f64 = grad.iter().map(|g| g * g).sum::<f64>().sqrt();
if grad_norm < self.tol {
iters = k;
break;
}
let x_new = if self.use_entropy {
Self::entropy_step(&x, &grad, self.eta)
} else {
x.iter()
.zip(&grad)
.map(|(xi, gi)| xi - self.eta * gi)
.collect()
};
let fx_new = f.eval(&x_new);
if fx_new < best_f {
best_f = fx_new;
best_x = x_new.clone();
}
x = x_new;
}
(best_x, best_f, iters)
}
pub fn bregman_kl(x: &[f64], y: &[f64]) -> f64 {
x.iter()
.zip(y)
.map(|(xi, yi)| {
if *xi <= 0.0 {
return 0.0;
}
xi * (xi / yi).ln() - xi + yi
})
.sum()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct RipVerifier {
pub sparsity: usize,
pub num_trials: usize,
}
impl RipVerifier {
pub fn new(sparsity: usize, num_trials: usize) -> Self {
Self {
sparsity,
num_trials,
}
}
fn mat_vec(a: &[Vec<f64>], x: &[f64]) -> Vec<f64> {
a.iter()
.map(|row| row.iter().zip(x).map(|(aij, xi)| aij * xi).sum::<f64>())
.collect()
}
pub fn estimate_rip_constant(&self, a: &[Vec<f64>]) -> (f64, f64) {
if a.is_empty() {
return (0.0, 0.0);
}
let n = a[0].len();
let s = self.sparsity.min(n);
let mut delta_lower = 0.0_f64;
let mut delta_upper = 0.0_f64;
for trial in 0..self.num_trials {
let mut x = vec![0.0_f64; n];
for k in 0..s {
let idx = (trial * s + k) % n;
x[idx] = if k % 2 == 0 { 1.0 } else { -1.0 };
}
let x_norm_sq: f64 = x.iter().map(|xi| xi * xi).sum();
if x_norm_sq < 1e-12 {
continue;
}
let ax = Self::mat_vec(a, &x);
let ax_norm_sq: f64 = ax.iter().map(|axi| axi * axi).sum();
let ratio = ax_norm_sq / x_norm_sq;
let dev = (ratio - 1.0).abs();
if dev > delta_upper {
delta_upper = dev;
}
let lower_dev = 1.0 - ratio;
if lower_dev > delta_lower {
delta_lower = lower_dev;
}
}
(delta_lower, delta_upper)
}
pub fn satisfies_rip(&self, a: &[Vec<f64>], delta: f64) -> bool {
let (_, upper) = self.estimate_rip_constant(a);
upper < delta
}
pub fn soft_threshold(x: &[f64], lambda: f64) -> Vec<f64> {
x.iter()
.map(|xi| xi.signum() * (xi.abs() - lambda).max(0.0))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct CuttingPlaneSolver {
pub max_iter: usize,
pub tol: f64,
pub trust_radius: f64,
}
impl CuttingPlaneSolver {
pub fn new(max_iter: usize, tol: f64, trust_radius: f64) -> Self {
Self {
max_iter,
tol,
trust_radius,
}
}
pub fn minimize<F: ConvexFunction>(&self, f: &F, x0: &[f64]) -> (Vec<f64>, f64, usize) {
let n = x0.len();
let mut x = x0.to_vec();
let mut cuts: Vec<(Vec<f64>, f64, Vec<f64>)> = Vec::new();
let mut best_x = x.clone();
let mut best_f = f.eval(&x);
let mut iters = self.max_iter;
for k in 0..self.max_iter {
let fk = f.eval(&x);
let gk = f.gradient(&x);
cuts.push((x.clone(), fk, gk.clone()));
if fk < best_f {
best_f = fk;
best_x = x.clone();
}
let mut z = x.clone();
let step_model = 0.01_f64 * self.trust_radius;
for _ in 0..200 {
let model_vals: Vec<f64> = cuts
.iter()
.map(|(xj, fj, gj)| {
fj + gj
.iter()
.zip(&z)
.zip(xj.iter())
.map(|((gi, zi), xji)| gi * (zi - xji))
.sum::<f64>()
})
.collect();
let active = model_vals
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
let grad_model = &cuts[active].2;
let mut z_new: Vec<f64> = z
.iter()
.zip(grad_model)
.map(|(zi, gi)| zi - step_model * gi)
.collect();
let dist_sq: f64 = z_new.iter().zip(&x).map(|(zi, xi)| (zi - xi).powi(2)).sum();
if dist_sq > self.trust_radius * self.trust_radius {
let scale = self.trust_radius / dist_sq.sqrt();
for i in 0..n {
z_new[i] = x[i] + scale * (z_new[i] - x[i]);
}
}
z = z_new;
}
let model_at_z: f64 = cuts
.iter()
.map(|(xj, fj, gj)| {
fj + gj
.iter()
.zip(&z)
.zip(xj.iter())
.map(|((gi, zi), xji)| gi * (zi - xji))
.sum::<f64>()
})
.fold(f64::NEG_INFINITY, f64::max);
let gap = best_f - model_at_z;
if gap < self.tol {
iters = k + 1;
break;
}
x = z;
}
(best_x, best_f, iters)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ProximalGradientSolver {
pub lipschitz: f64,
pub max_iter: usize,
pub tol: f64,
pub accelerated: bool,
}
impl ProximalGradientSolver {
pub fn new(lipschitz: f64, max_iter: usize, tol: f64, accelerated: bool) -> Self {
Self {
lipschitz,
max_iter,
tol,
accelerated,
}
}
pub fn minimize<F, G>(&self, smooth: &F, regularizer: &G, x0: &[f64]) -> (Vec<f64>, usize)
where
F: ConvexFunction,
G: ProxableFunction,
{
let n = x0.len();
let step = 1.0 / self.lipschitz;
let mut x = x0.to_vec();
let mut y = x.clone();
let mut t = 1.0_f64;
let mut iters = self.max_iter;
for k in 1..=self.max_iter {
let grad = smooth.gradient(&y);
let v: Vec<f64> = y.iter().zip(&grad).map(|(yi, gi)| yi - step * gi).collect();
let x_new = regularizer.prox(&v, step);
let diff_norm: f64 = x_new
.iter()
.zip(&x)
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
if self.accelerated {
let t_new = (1.0 + (1.0 + 4.0 * t * t).sqrt()) / 2.0;
let beta = (t - 1.0) / t_new;
let mut y_new = vec![0.0_f64; n];
for i in 0..n {
y_new[i] = x_new[i] + beta * (x_new[i] - x[i]);
}
t = t_new;
y = y_new;
} else {
y = x_new.clone();
}
x = x_new;
if diff_norm < self.tol {
iters = k;
break;
}
}
(x, iters)
}
pub fn estimate_lipschitz<F: ConvexFunction>(f: &F, x: &[f64], num_trials: usize) -> f64 {
let eps = 1e-5_f64;
let n = x.len();
let grad0 = f.gradient(x);
let mut max_l = 0.0_f64;
for i in 0..num_trials.min(n) {
let mut x_pert = x.to_vec();
x_pert[i] += eps;
let grad_pert = f.gradient(&x_pert);
let diff_norm: f64 = grad_pert
.iter()
.zip(&grad0)
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
max_l = max_l.max(diff_norm / eps);
}
max_l.max(1e-8)
}
}
#[derive(Debug, Clone)]
pub struct FISTASolver {
pub lipschitz: f64,
pub max_iter: usize,
pub tol: f64,
}
impl FISTASolver {
pub fn new(lipschitz: f64, max_iter: usize, tol: f64) -> Self {
Self {
lipschitz,
max_iter,
tol,
}
}
pub fn minimize<F, G>(&self, smooth: &F, regularizer: &G, x0: &[f64]) -> (Vec<f64>, usize)
where
F: ConvexFunction,
G: ProxableFunction,
{
let n = x0.len();
let step = 1.0 / self.lipschitz;
let mut x = x0.to_vec();
let mut y = x.clone();
let mut t = 1.0_f64;
let mut iters = self.max_iter;
for k in 1..=self.max_iter {
let grad = smooth.gradient(&y);
let v: Vec<f64> = y.iter().zip(&grad).map(|(yi, gi)| yi - step * gi).collect();
let x_new = regularizer.prox(&v, step);
let t_new = (1.0 + (1.0 + 4.0 * t * t).sqrt()) / 2.0;
let beta = (t - 1.0) / t_new;
let mut diff_norm = 0.0_f64;
let mut y_new = vec![0.0_f64; n];
for i in 0..n {
y_new[i] = x_new[i] + beta * (x_new[i] - x[i]);
diff_norm += (x_new[i] - x[i]).powi(2);
}
x = x_new;
y = y_new;
t = t_new;
if diff_norm.sqrt() < self.tol {
iters = k;
break;
}
}
(x, iters)
}
}
#[derive(Debug, Clone)]
pub struct L1NormFunction {
pub lambda: f64,
}
impl L1NormFunction {
pub fn new(lambda: f64) -> Self {
Self { lambda }
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SinkhornSolver {
pub epsilon: f64,
pub max_iter: usize,
pub tol: f64,
}
impl SinkhornSolver {
pub fn new(epsilon: f64, max_iter: usize, tol: f64) -> Self {
Self {
epsilon,
max_iter,
tol,
}
}
fn gibbs_kernel(cost: &[Vec<f64>], epsilon: f64) -> Vec<Vec<f64>> {
cost.iter()
.map(|row| row.iter().map(|&c| (-c / epsilon).exp()).collect())
.collect()
}
pub fn solve(&self, mu: &[f64], nu: &[f64], cost: &[Vec<f64>]) -> (Vec<Vec<f64>>, f64) {
let m = mu.len();
let n = nu.len();
let k = Self::gibbs_kernel(cost, self.epsilon);
let mut u = vec![1.0_f64; m];
let mut v = vec![1.0_f64; n];
for _ in 0..self.max_iter {
let kv: Vec<f64> = (0..m)
.map(|i| k[i].iter().zip(&v).map(|(kij, vj)| kij * vj).sum::<f64>())
.collect();
let u_new: Vec<f64> = mu
.iter()
.zip(&kv)
.map(|(mi, kvi)| mi / kvi.max(1e-300))
.collect();
let kt_u: Vec<f64> = (0..n)
.map(|j| k.iter().zip(&u_new).map(|(ki, ui)| ki[j] * ui).sum::<f64>())
.collect();
let v_new: Vec<f64> = nu
.iter()
.zip(&kt_u)
.map(|(nj, ktuj)| nj / ktuj.max(1e-300))
.collect();
let err: f64 = u_new
.iter()
.zip(&u)
.map(|(a, b)| (a - b).abs())
.sum::<f64>()
+ v_new
.iter()
.zip(&v)
.map(|(a, b)| (a - b).abs())
.sum::<f64>();
u = u_new;
v = v_new;
if err < self.tol {
break;
}
}
let mut gamma = vec![vec![0.0_f64; n]; m];
let mut w_cost = 0.0_f64;
for i in 0..m {
for j in 0..n {
gamma[i][j] = u[i] * k[i][j] * v[j];
w_cost += gamma[i][j] * cost[i][j];
}
}
(gamma, w_cost)
}
pub fn wasserstein2_1d(
points_mu: &[f64],
weights_mu: &[f64],
points_nu: &[f64],
weights_nu: &[f64],
) -> f64 {
let m = points_mu.len();
let n = points_nu.len();
let cost: Vec<Vec<f64>> = (0..m)
.map(|i| {
(0..n)
.map(|j| (points_mu[i] - points_nu[j]).powi(2))
.collect()
})
.collect();
let solver = Self::new(0.01, 200, 1e-8);
let (_, w2) = solver.solve(weights_mu, weights_nu, &cost);
w2
}
}
#[derive(Debug, Clone)]
pub struct GeometricProgramSolver {
pub max_iter: usize,
pub step_size: f64,
pub tol: f64,
}
impl GeometricProgramSolver {
pub fn new(max_iter: usize, step_size: f64, tol: f64) -> Self {
Self {
max_iter,
step_size,
tol,
}
}
pub fn eval_log_monomial(log_c: f64, exponents: &[f64], y: &[f64]) -> f64 {
let dot: f64 = exponents.iter().zip(y).map(|(ai, yi)| ai * yi).sum();
log_c + dot
}
pub fn log_sum_exp_posynomial(monomials: &[(f64, Vec<f64>)], y: &[f64]) -> f64 {
let vals: Vec<f64> = monomials
.iter()
.map(|(lc, exp)| Self::eval_log_monomial(*lc, exp, y))
.collect();
let max_val = vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
if max_val.is_infinite() {
return max_val;
}
max_val + vals.iter().map(|v| (v - max_val).exp()).sum::<f64>().ln()
}
pub fn solve(
&self,
objective: &[(f64, Vec<f64>)],
constraints: &[Vec<(f64, Vec<f64>)>],
y0: &[f64],
) -> (Vec<f64>, f64) {
let n = y0.len();
let mut y = y0.to_vec();
for _ in 0..self.max_iter {
let obj_vals: Vec<f64> = objective
.iter()
.map(|(lc, exp)| Self::eval_log_monomial(*lc, exp, &y))
.collect();
let max_v = obj_vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let weights: Vec<f64> = obj_vals.iter().map(|v| (v - max_v).exp()).collect();
let w_sum: f64 = weights.iter().sum();
let mut grad = vec![0.0_f64; n];
for (k, (_, exp)) in objective.iter().enumerate() {
let wk = weights[k] / w_sum;
for i in 0..n {
grad[i] += wk * exp[i];
}
}
for constraint in constraints {
let c_lse = Self::log_sum_exp_posynomial(constraint, &y);
if c_lse > 0.0 {
let rho = 10.0_f64;
let c_vals: Vec<f64> = constraint
.iter()
.map(|(lc, exp)| Self::eval_log_monomial(*lc, exp, &y))
.collect();
let c_max = c_vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let c_weights: Vec<f64> = c_vals.iter().map(|v| (v - c_max).exp()).collect();
let c_wsum: f64 = c_weights.iter().sum();
for (k, (_, exp)) in constraint.iter().enumerate() {
let wk = c_weights[k] / c_wsum;
for i in 0..n {
grad[i] += rho * wk * exp[i];
}
}
}
}
let grad_norm: f64 = grad.iter().map(|g| g * g).sum::<f64>().sqrt();
if grad_norm < self.tol {
break;
}
for i in 0..n {
y[i] -= self.step_size * grad[i];
}
}
let obj_val = Self::log_sum_exp_posynomial(objective, &y).exp();
(y, obj_val)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct OnlineLearner {
pub eta: f64,
pub dim: usize,
pub cumulative_grad: Vec<f64>,
pub round: usize,
pub cumulative_loss: f64,
}
impl OnlineLearner {
pub fn new(eta: f64, dim: usize) -> Self {
Self {
eta,
dim,
cumulative_grad: vec![0.0_f64; dim],
round: 0,
cumulative_loss: 0.0,
}
}
pub fn current_decision(&self) -> Vec<f64> {
self.cumulative_grad.iter().map(|g| -self.eta * g).collect()
}
pub fn update(&mut self, grad: &[f64]) -> f64 {
let x_t = self.current_decision();
let loss_t: f64 = grad.iter().zip(&x_t).map(|(gi, xi)| gi * xi).sum();
for (cg, g) in self.cumulative_grad.iter_mut().zip(grad) {
*cg += g;
}
self.cumulative_loss += loss_t;
self.round += 1;
loss_t
}
pub fn regret_bound(&self, optimal_norm: f64, grad_bound: f64) -> f64 {
optimal_norm * grad_bound * (self.round as f64).sqrt() / self.eta
+ 0.5 * self.eta * grad_bound * grad_bound * self.round as f64
}
pub fn reset(&mut self) {
self.cumulative_grad = vec![0.0_f64; self.dim];
self.round = 0;
self.cumulative_loss = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct BundleMethodSolver {
pub mu: f64,
pub m_l: f64,
pub max_bundle_size: usize,
pub max_iter: usize,
pub tol: f64,
}
impl BundleMethodSolver {
pub fn new(mu: f64, m_l: f64, max_bundle_size: usize, max_iter: usize, tol: f64) -> Self {
Self {
mu,
m_l,
max_bundle_size,
max_iter,
tol,
}
}
pub fn minimize<F: ConvexFunction>(&self, f: &F, x0: &[f64]) -> (Vec<f64>, f64, usize) {
let n = x0.len();
let mut xhat = x0.to_vec();
let mut fhat = f.eval(&xhat);
let mut bundle: Vec<(Vec<f64>, f64)> = Vec::new();
let g0 = f.gradient(&xhat);
bundle.push((g0, 0.0));
let mut iters = self.max_iter;
for k in 0..self.max_iter {
let mut d = vec![0.0_f64; n];
let step_d = 1.0 / (self.mu + 1.0);
for _ in 0..500 {
let active_val = bundle
.iter()
.map(|(gj, alphaj)| {
let dot: f64 = gj.iter().zip(&d).map(|(gi, di)| gi * di).sum();
-alphaj + dot
})
.enumerate()
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
let (gj, _alphaj) = &bundle[active_val];
let grad_d: Vec<f64> = gj
.iter()
.zip(&d)
.map(|(gi, di)| gi + self.mu * di)
.collect();
let grad_norm: f64 = grad_d.iter().map(|g| g * g).sum::<f64>().sqrt();
if grad_norm < 1e-10 {
break;
}
for i in 0..n {
d[i] -= step_d * grad_d[i];
}
}
let x_cand: Vec<f64> = xhat.iter().zip(&d).map(|(xi, di)| xi + di).collect();
let f_cand = f.eval(&x_cand);
let g_cand = f.gradient(&x_cand);
let delta = fhat - f_cand;
let model_pred: f64 = bundle
.iter()
.map(|(gj, alphaj)| {
let dot: f64 = gj.iter().zip(&d).map(|(gi, di)| gi * di).sum();
-alphaj + dot
})
.fold(f64::NEG_INFINITY, f64::max);
let model_decrease = -model_pred;
if model_decrease.abs() < self.tol {
iters = k + 1;
break;
}
let alpha_new: f64 = fhat
- f_cand
- g_cand
.iter()
.zip(&d)
.map(|(gi, di)| gi * (-di))
.sum::<f64>();
if model_decrease > 1e-12 && delta >= self.m_l * model_decrease {
xhat = x_cand.clone();
fhat = f_cand;
bundle.clear();
bundle.push((g_cand, 0.0_f64.max(alpha_new)));
} else {
bundle.push((g_cand, 0.0_f64.max(alpha_new)));
if bundle.len() > self.max_bundle_size {
bundle.remove(0);
}
}
}
(xhat, fhat, iters)
}
}
#[derive(Debug, Clone)]
pub struct ProjectedGradient {
pub learning_rate: f64,
pub max_iter: usize,
pub tol: f64,
pub lb: Vec<f64>,
pub ub: Vec<f64>,
}
impl ProjectedGradient {
pub fn new(lr: f64, max_iter: usize, tol: f64, lb: Vec<f64>, ub: Vec<f64>) -> Self {
Self {
learning_rate: lr,
max_iter,
tol,
lb,
ub,
}
}
pub fn project(&self, x: &[f64]) -> Vec<f64> {
x.iter()
.enumerate()
.map(|(i, &xi)| xi.clamp(self.lb[i], self.ub[i]))
.collect()
}
pub fn minimize<F: ConvexFunction>(&self, f: &F, x0: &[f64]) -> (Vec<f64>, f64) {
let mut x = self.project(x0);
for _ in 0..self.max_iter {
let grad = f.gradient(&x);
let grad_norm: f64 = grad.iter().map(|g| g * g).sum::<f64>().sqrt();
if grad_norm < self.tol {
break;
}
let x_new: Vec<f64> = x
.iter()
.zip(&grad)
.map(|(xi, gi)| xi - self.learning_rate * gi)
.collect();
x = self.project(&x_new);
}
let fval = f.eval(&x);
(x, fval)
}
}
#[derive(Debug, Clone)]
pub struct QuadraticFunction {
pub coeffs: Vec<Vec<f64>>,
pub linear: Vec<f64>,
pub constant: f64,
}
impl QuadraticFunction {
pub fn new(q: Vec<Vec<f64>>, c: Vec<f64>, d: f64) -> Self {
Self {
coeffs: q,
linear: c,
constant: d,
}
}
}
#[derive(Debug, Clone)]
pub struct ADMM {
pub rho: f64,
pub max_iter: usize,
pub tol: f64,
}
impl ADMM {
pub fn new(rho: f64) -> Self {
Self {
rho,
max_iter: 1000,
tol: 1e-6,
}
}
pub fn solve_lasso(&self, a: &[Vec<f64>], b: &[f64], lambda: f64) -> Vec<f64> {
let _ = (a, b, lambda, self.rho, self.max_iter, self.tol);
let n = a.first().map(|row| row.len()).unwrap_or(0);
vec![0.0; n]
}
}
#[derive(Debug, Clone)]
pub struct SDPRelaxation {
pub q: Vec<Vec<f64>>,
pub c: Vec<f64>,
pub a_mat: Vec<Vec<f64>>,
pub b_vec: Vec<f64>,
}
impl SDPRelaxation {
pub fn new(q: Vec<Vec<f64>>, c: Vec<f64>, a_mat: Vec<Vec<f64>>, b_vec: Vec<f64>) -> Self {
Self { q, c, a_mat, b_vec }
}
pub fn dim(&self) -> usize {
self.c.len()
}
pub fn solve(&self) -> f64 {
let _ = (&self.q, &self.c, &self.a_mat, &self.b_vec);
0.0
}
pub fn is_psd(mat: &[Vec<f64>]) -> bool {
let n = mat.len();
for size in 1..=n {
let mut sub: Vec<Vec<f64>> = (0..size).map(|i| mat[i][..size].to_vec()).collect();
let mut det = 1.0_f64;
for col in 0..size {
let pivot_row = (col..size).find(|&r| sub[r][col].abs() > 1e-12);
let pr = match pivot_row {
Some(r) => r,
None => return false,
};
if pr != col {
sub.swap(col, pr);
det = -det;
}
det *= sub[col][col];
let pv = sub[col][col];
for r in (col + 1)..size {
let factor = sub[r][col] / pv;
for c in col..size {
let val = sub[col][c];
sub[r][c] -= factor * val;
}
}
}
if det < -1e-9 {
return false;
}
}
true
}
}