use good_lp::{SolverModel, Solution, Expression, Variable, ProblemVariables, default_solver};
use itertools::Itertools;
use num::ToPrimitive;
use sprs::linalg::ordering::start;
use crate::algebra::vectors::entries::{KeyValGet};
use std::fmt::Debug;
use std::hash::Hash;
use std::collections::HashMap;
use derive_getters::{Getters, Dissolve};
use derive_new::new;
#[derive(new,Clone,Debug,Getters,Dissolve)]
pub struct SolutionL1< Key, Coefficient >{
pub x: Vec< (Key, f64) >,
pub b: Vec< (Key, Coefficient) >,
pub y: Vec< (Key, f64) >,
pub cost_b: f64,
pub cost_y: f64,
pub construction_time: f64, pub solve_time: f64, }
pub fn minimize_l1<
Key,
ConstraintMatrix,
ConstraintVector,
BoundVector,
ColumnIndexIterable,
CostVector,
Coefficient,
>
(
mut a: ConstraintMatrix,
b: BoundVector,
mut c: CostVector,
column_indices: ColumnIndexIterable,
verbose: bool, )
->
Result< SolutionL1<Key, Coefficient>, String >
where
ConstraintMatrix: FnMut( & Key )->ConstraintVector,
ConstraintVector: IntoIterator,
ConstraintVector::Item: KeyValGet < Key = Key, Val = Coefficient >,
BoundVector: IntoIterator,
BoundVector::Item: KeyValGet < Key = Key, Val = Coefficient >,
Coefficient: Clone + ToPrimitive, Key: Clone + Debug + Hash + std::cmp::Eq, ColumnIndexIterable: Clone + IntoIterator< Item = Key >,
CostVector: FnMut( & Key) -> f64,
{
let start = std::time::Instant::now();
let mut variables = ProblemVariables::new();
pub struct Row{ lhs: Expression, rhs: Variable }
let mut key_to_row = HashMap::<Key,Row>::new(); let mut key_to_colvar = HashMap::<Key,Variable>::new();
let b = b.into_iter()
.map( |entry| (entry.key(), entry.val() ) )
.collect_vec();
let cost_b
= b.iter().map(
|(k,v)|
c(k) * v.to_f64().unwrap().abs() ).sum();
for entry in b.iter() {
let key = entry.key();
if key_to_row.contains_key( &key )
{ return Err("Error in OAT's optimization utility: the user provided a cost vector `b` that has multiple entries with the same index.".to_string()) }
let mut expression = Expression::with_capacity(1);
expression += entry.val().to_f64().unwrap();
let y = variables.add_variable();
key_to_row.insert( key, Row{ lhs: expression, rhs: y } );
}
let mut num_entries = 0;
for column_index in column_indices.into_iter() {
let x = variables.add_variable();
key_to_colvar.insert( column_index.clone(), x );
let column_entries = a( & column_index );
for entry in column_entries.into_iter() {
num_entries += 1;
let key = entry.key();
let coeff = entry.val().to_f64().unwrap();
if let Some(row) = key_to_row.get_mut( &key ) {
row.lhs.add_mul( coeff, x );
} else {
let mut expression = Expression::with_capacity(1);
expression.add_mul( coeff, x );
let y = variables.add_variable();
key_to_row.insert( key, Row{ lhs: expression, rhs: y } );
}
}
}
let mut objective = Expression::with_capacity( key_to_row.len() );
for (key,row) in key_to_row.iter() {
let coeff = c(key).to_f64().unwrap();
objective.add_mul( coeff, row.rhs );
}
let mut problem = variables
.minimise( objective.clone() )
.using( default_solver );
for row in key_to_row.values() {
problem = problem.with( row.lhs.clone() << row.rhs );
problem = problem.with( - row.lhs.clone() << row.rhs );
}
let construction_duration = start.elapsed();
if verbose {
println!("Finished construcing L1 optimization program.");
println!("Construction took {:?} seconds.", construction_duration.as_secs_f64());
println!("Constraint matrix has {:?} nonzero entries.", num_entries);
println!("Passing program to solver.\n");
}
let start = std::time::Instant::now();
let solution = problem.solve().unwrap();
let solve_duration = start.elapsed();
if verbose {
println!("\nFinished solving.\n");
println!("Solver took {:?} seconds in walltime.", solve_duration.as_secs_f64());
}
let cost_y = solution.eval(objective);
let x = key_to_colvar.iter().map(
| ( key, var ) |
(
key.clone(),
solution.value(*var)
)
)
.filter(|x| x.1 != 0.0 )
.collect_vec();
let y = key_to_row.iter().map(
|(key, row)| -> (Key,f64)
{
(
key.clone(),
row.lhs.eval_with(&solution)
)
}
)
.filter( |x: &(Key,f64)| x.1 != 0.0 )
.collect_vec();
if verbose {
println!("GOODLP solution: {:?}", solution.into_inner() );
}
let answer = SolutionL1{
cost_b,
cost_y,
b,
y,
x,
construction_time: construction_duration.as_secs_f64(),
solve_time: solve_duration.as_secs_f64(),
};
Ok(answer)
}
pub fn minimize_l1_kernel<
Key,
ConstraintMatrix,
ConstraintMatrixColumn,
ConstraintVector,
ColumnIndexIterable,
CostVector,
Coefficient,
>
(
mut a: ConstraintMatrix,
b: ConstraintVector,
mut c: CostVector,
column_indices: ColumnIndexIterable,
)
->
Result< SolutionL1<Key, Coefficient>, String >
where
ConstraintMatrix: FnMut( & Key )->ConstraintMatrixColumn,
ConstraintVector: IntoIterator,
ConstraintVector::Item: KeyValGet< Key = Key, Val = Coefficient >,
ConstraintMatrixColumn: IntoIterator,
ConstraintMatrixColumn::Item: KeyValGet< Key = Key, Val = Coefficient >,
Coefficient: Clone + ToPrimitive, Key: Clone + Debug + Hash + std::cmp::Eq, ColumnIndexIterable: Clone + IntoIterator< Item = Key >,
CostVector: FnMut( & Key) -> f64,
{
let start = std::time::Instant::now();
let mut variables = ProblemVariables::new();
pub struct Col{ x: Variable, y: Variable, x_plus_b: Expression, }
let mut key_to_col = HashMap::<Key,Col>::new(); let mut key_to_row_expr = HashMap::<Key,Expression>::new();
let b: HashMap<Key, Coefficient> = b.into_iter()
.map( |entry| (entry.key(), entry.val() ) )
.collect();
let cost_b
= b.iter().map(
|(k,v)|
c(k) * v.to_f64().unwrap().abs() ).sum();
for column_index in column_indices.into_iter() {
let x = variables.add_variable();
let y = variables.add_variable();
let mut x_plus_b = Expression::with_capacity(1);
x_plus_b.add_mul( 1.0, x );
if let Some(coeff) = b.get( & column_index ) {
x_plus_b += coeff.to_f64().unwrap();
}
key_to_col.insert(
column_index.clone(),
Col{ x, y, x_plus_b }
);
let column_entries = a( & column_index );
for entry in column_entries.into_iter() {
let key = entry.key();
let coeff = entry.val().to_f64().unwrap();
if let Some(row) = key_to_row_expr.get_mut( &key ) {
row.add_mul( coeff, x );
} else {
let mut expression = Expression::with_capacity(1);
expression.add_mul( coeff, x );
key_to_row_expr.insert( key, expression );
}
}
}
let mut objective = Expression::with_capacity( key_to_col.len() );
for (key, col) in key_to_col.iter() {
let coeff = c(key).to_f64().unwrap();
objective.add_mul( coeff, col.y );
}
let mut problem = variables
.minimise( objective.clone() )
.using( default_solver );
for col in key_to_col.values() {
problem = problem.with( col.x_plus_b.clone() << col.y );
problem = problem.with( - col.x_plus_b.clone() << col.y );
}
let construction_duration = start.elapsed();
let start = std::time::Instant::now();
let solution = problem.solve().unwrap();
let solve_duration = start.elapsed();
let cost_y = solution.eval(objective);
let x = key_to_col.iter().map(
| ( key, col ) |
(
key.clone(),
solution.value(col.x)
)
)
.filter(|x| x.1 != 0.0 )
.collect_vec();
let y = key_to_col.iter().map(
|(key, col)| -> (Key,f64)
{
(
key.clone(),
solution.value(col.y)
)
}
)
.filter( |x: &(Key,f64)| x.1 != 0.0 )
.collect_vec();
let b: Vec<(Key, Coefficient)> = b.into_iter().collect();
let answer = SolutionL1{
cost_b,
cost_y,
b,
y,
x,
construction_time: construction_duration.as_secs_f64(),
solve_time: solve_duration.as_secs_f64(),
};
Ok(answer)
}
#[cfg(test)]
mod tests {
#[test]
fn test_l1_good_a() {
use crate::utilities::optimization::minimize_l1::minimize_l1;
let constraint_data = vec![ vec![ (0,1.), (1,1.) ] ];
let a = |i: & usize | -> Vec<(usize,f64)> { constraint_data[*i].clone() }; let b = vec![ (0usize,2f64), (1usize,2f64) ]; let c = |_x: & usize| -> f64 { 1.0 }; let column_indices = vec![0];
let verbose = true;
let solution = minimize_l1( a,b.clone(),c, column_indices, verbose).unwrap();
println!("{:?}", solution);
assert_eq!( solution.x(), &vec![(0,-2.0)] );
assert_eq!( solution.b(), &b );
assert_eq!( solution.y(), &Vec::<(usize,f64)>::new() );
assert_eq!( solution.cost_b(), &4.0 );
assert_eq!( solution.cost_y(), &0.0 );
}
#[test]
fn test_l1_good_b() {
use crate::utilities::optimization::minimize_l1::minimize_l1;
let constraint_data = vec![ vec![ (0,1.), (1,1.), (2,1.), ] ];
let a = |i: & usize | -> Vec<(usize,f64)> { constraint_data[*i].clone() };
let b = vec![ (0,1.), (1,1.), ];
let c = |_x: & usize| -> f64 { 1.0 };
let column_indices = vec![0];
let verbose = true;
let solution = minimize_l1( a,b.clone(),c, column_indices, verbose).unwrap();
println!("{:?}", solution);
assert_eq!( solution.x(), &vec![(0,-1.0)] );
assert_eq!( solution.b(), &b );
assert_eq!( solution.y(), &vec![(2,-1.0)] );
assert_eq!( solution.cost_b(), &2.0 );
assert_eq!( solution.cost_y(), &1.0 );
}
}