use crate::sparse::CscMatrix;
use crate::tolerances::UNDERFLOW_GUARD;
const LP_RUIZ_MAX_SWEEPS: usize = 20;
const LP_RUIZ_CONV_TOL: f64 = 1e-4;
pub struct RuizScaler;
impl RuizScaler {
pub fn scale(
matrix: &CscMatrix,
b: &[f64],
c: &[f64],
) -> (CscMatrix, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
let m = matrix.nrows;
let n = matrix.ncols;
let mut cumul_row = vec![1.0f64; m];
let mut cumul_col = vec![1.0f64; n];
let mut a = matrix.clone();
let mut cur_b = b.to_vec();
let mut cur_c = c.to_vec();
for _ in 0..LP_RUIZ_MAX_SWEEPS {
let mut row_max = vec![0.0f64; m];
for k in 0..a.row_ind.len() {
let row = a.row_ind[k];
let v = a.values[k].abs();
if v > row_max[row] {
row_max[row] = v;
}
}
let mut col_max = vec![0.0f64; n];
for (j, col_max_j) in col_max.iter_mut().enumerate().take(n) {
let start = a.col_ptr[j];
let end = a.col_ptr[j + 1];
for k in start..end {
let v = a.values[k].abs();
if v > *col_max_j {
*col_max_j = v;
}
}
}
let row_factor: Vec<f64> = row_max
.iter()
.map(|&mx| if mx > UNDERFLOW_GUARD { 1.0 / mx.sqrt() } else { 1.0 })
.collect();
let col_factor: Vec<f64> = col_max
.iter()
.map(|&mx| if mx > UNDERFLOW_GUARD { 1.0 / mx.sqrt() } else { 1.0 })
.collect();
let max_change = row_factor
.iter()
.chain(col_factor.iter())
.map(|&f| (f - 1.0).abs())
.fold(0.0f64, f64::max);
for (j, &cf) in col_factor.iter().enumerate().take(n) {
let start = a.col_ptr[j];
let end = a.col_ptr[j + 1];
for k in start..end {
let row = a.row_ind[k];
a.values[k] *= row_factor[row] * cf;
}
}
for i in 0..m {
cur_b[i] *= row_factor[i];
}
for j in 0..n {
cur_c[j] *= col_factor[j];
}
for i in 0..m {
cumul_row[i] *= row_factor[i];
}
for j in 0..n {
cumul_col[j] *= col_factor[j];
}
if max_change < LP_RUIZ_CONV_TOL {
break;
}
}
(a, cur_b, cur_c, cumul_row, cumul_col)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_dense_csc(data: &[Vec<f64>], nrows: usize, ncols: usize) -> CscMatrix {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for (i, row) in data.iter().enumerate().take(nrows) {
for (j, &val) in row.iter().enumerate().take(ncols) {
if val.abs() > 1e-15 {
rows.push(i);
cols.push(j);
vals.push(val);
}
}
}
CscMatrix::from_triplets(&rows, &cols, &vals, nrows, ncols).unwrap()
}
#[test]
fn test_ruiz_scale_identity() {
let a = make_dense_csc(
&[vec![1.0, 0.0], vec![0.0, 1.0]],
2,
2,
);
let b = vec![1.0, 2.0];
let c = vec![1.0, 1.0];
let (_, scaled_b, scaled_c, row_scale, col_scale) = RuizScaler::scale(&a, &b, &c);
for &s in row_scale.iter().chain(col_scale.iter()) {
assert!((s - 1.0).abs() < 0.01, "Scale {} far from 1", s);
}
for i in 0..2 {
assert!((scaled_b[i] - b[i] * row_scale[i]).abs() < 1e-10);
assert!((scaled_c[i] - c[i] * col_scale[i]).abs() < 1e-10);
}
}
#[test]
fn test_ruiz_scale_unbalanced() {
let a = make_dense_csc(
&[vec![1000.0, 1.0], vec![1.0, 0.001]],
2,
2,
);
let b = vec![1.0, 1.0];
let c = vec![1.0, 1.0];
let (scaled_a, _, _, _, _) = RuizScaler::scale(&a, &b, &c);
for k in 0..scaled_a.values.len() {
assert!(
scaled_a.values[k].abs() < 2.0,
"Entry {} too large after scaling",
scaled_a.values[k]
);
}
}
}