#[derive(Debug, Clone)]
pub struct SDPProblem {
pub n: usize,
pub c: Vec<f64>,
pub constraints: Vec<Vec<f64>>,
pub b: Vec<f64>,
}
impl SDPProblem {
pub fn new(n: usize) -> Self {
Self {
n,
c: vec![0.0; n * n],
constraints: Vec::new(),
b: Vec::new(),
}
}
pub fn set_objective(&mut self, c: Vec<f64>) {
assert_eq!(c.len(), self.n * self.n);
self.c = c;
}
pub fn add_constraint(&mut self, a: Vec<f64>, bi: f64) {
assert_eq!(a.len(), self.n * self.n);
self.constraints.push(a);
self.b.push(bi);
}
pub fn num_constraints(&self) -> usize {
self.constraints.len()
}
}
#[derive(Debug, Clone)]
pub struct SDPSolution {
pub x: Vec<f64>,
pub value: f64,
pub status: SDPStatus,
pub iterations: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SDPStatus {
Optimal,
Infeasible,
Unbounded,
MaxIterations,
NumericalError,
}
pub struct SDPSolver {
pub max_iters: usize,
pub tolerance: f64,
pub step_size: f64,
}
impl SDPSolver {
pub fn new() -> Self {
Self {
max_iters: 1000,
tolerance: 1e-6,
step_size: 0.01,
}
}
pub fn solve(&self, problem: &SDPProblem) -> SDPSolution {
let n = problem.n;
let m = problem.num_constraints();
if n == 0 {
return SDPSolution {
x: vec![],
value: 0.0,
status: SDPStatus::Optimal,
iterations: 0,
};
}
let mut x = vec![0.0; n * n];
for i in 0..n {
x[i * n + i] = 1.0;
}
let mut dual = vec![0.0; m];
let rho = 1.0;
for iter in 0..self.max_iters {
let mut grad = problem.c.clone();
for (j, (a, &d)) in problem.constraints.iter().zip(dual.iter()).enumerate() {
let ax: f64 = (0..n * n).map(|k| a[k] * x[k]).sum();
let residual = ax - problem.b[j];
for k in 0..n * n {
grad[k] += (d + rho * residual) * a[k];
}
}
for k in 0..n * n {
x[k] -= self.step_size * grad[k];
}
self.project_psd(&mut x, n);
let mut max_violation = 0.0f64;
for (j, a) in problem.constraints.iter().enumerate() {
let ax: f64 = (0..n * n).map(|k| a[k] * x[k]).sum();
let residual = ax - problem.b[j];
dual[j] += rho * residual;
max_violation = max_violation.max(residual.abs());
}
if max_violation < self.tolerance {
let value: f64 = (0..n * n).map(|k| problem.c[k] * x[k]).sum();
return SDPSolution {
x,
value,
status: SDPStatus::Optimal,
iterations: iter + 1,
};
}
}
let value: f64 = (0..n * n).map(|k| problem.c[k] * x[k]).sum();
SDPSolution {
x,
value,
status: SDPStatus::MaxIterations,
iterations: self.max_iters,
}
}
fn project_psd(&self, x: &mut [f64], n: usize) {
for i in 0..n {
for j in i + 1..n {
let avg = (x[i * n + j] + x[j * n + i]) / 2.0;
x[i * n + j] = avg;
x[j * n + i] = avg;
}
}
if n <= 10 {
self.project_psd_small(x, n);
} else {
for i in 0..n {
let mut row_sum = 0.0;
for j in 0..n {
if i != j {
row_sum += x[i * n + j].abs();
}
}
x[i * n + i] = x[i * n + i].max(row_sum + 0.01);
}
}
}
fn project_psd_small(&self, x: &mut [f64], n: usize) {
let mut v: Vec<f64> = (0..n).map(|i| 1.0 / (n as f64).sqrt()).collect();
let mut lambda_max = 0.0;
for _ in 0..20 {
let mut y = vec![0.0; n];
for i in 0..n {
for j in 0..n {
y[i] += x[i * n + j] * v[j];
}
}
let norm: f64 = y.iter().map(|&yi| yi * yi).sum::<f64>().sqrt();
lambda_max = v.iter().zip(y.iter()).map(|(&vi, &yi)| vi * yi).sum();
if norm > 1e-15 {
for i in 0..n {
v[i] = y[i] / norm;
}
}
}
let shift = lambda_max.abs() + 1.0;
let mut v: Vec<f64> = (0..n).map(|i| 1.0 / (n as f64).sqrt()).collect();
let mut lambda_min = 0.0;
for _ in 0..20 {
let mut y = vec![0.0; n];
for i in 0..n {
for j in 0..n {
let val = if i == j {
shift - x[i * n + j]
} else {
-x[i * n + j]
};
y[i] += val * v[j];
}
}
let norm: f64 = y.iter().map(|&yi| yi * yi).sum::<f64>().sqrt();
let lambda_shifted: f64 = v.iter().zip(y.iter()).map(|(&vi, &yi)| vi * yi).sum();
lambda_min = shift - lambda_shifted;
if norm > 1e-15 {
for i in 0..n {
v[i] = y[i] / norm;
}
}
}
if lambda_min < 0.0 {
let alpha = -lambda_min + 0.01;
for i in 0..n {
x[i * n + i] += alpha;
}
}
}
}
impl Default for SDPSolver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdp_simple() {
let mut problem = SDPProblem::new(2);
let mut c = vec![0.0; 4];
c[0] = 1.0; c[3] = 1.0; problem.set_objective(c);
let mut a = vec![0.0; 4];
a[0] = 1.0;
problem.add_constraint(a, 1.0);
let solver = SDPSolver::new();
let solution = solver.solve(&problem);
assert!(
solution.status == SDPStatus::Optimal || solution.status == SDPStatus::MaxIterations
);
}
#[test]
fn test_sdp_feasibility() {
let mut problem = SDPProblem::new(2);
problem.set_objective(vec![0.0; 4]);
let mut a1 = vec![0.0; 4];
a1[0] = 1.0;
problem.add_constraint(a1, 1.0);
let mut a2 = vec![0.0; 4];
a2[3] = 1.0;
problem.add_constraint(a2, 1.0);
let solver = SDPSolver::new();
let solution = solver.solve(&problem);
let x00 = solution.x[0];
let x11 = solution.x[3];
assert!((x00 - 1.0).abs() < 0.1 || solution.status == SDPStatus::MaxIterations);
assert!((x11 - 1.0).abs() < 0.1 || solution.status == SDPStatus::MaxIterations);
}
}