use gag::Gag;
use grb::attribute::ModelIntAttr::LicenseExpiration;
use grb::constr::IneqExpr;
use grb::prelude::*;
use grb::expr::LinExpr;
use itertools::Itertools;
use num::ToPrimitive;
use num::rational::Ratio;
use ordered_float::OrderedFloat;
use crate::utilities::iterators::merge::hit::HitMergeByPredicateTrait;
use crate::algebra::vectors::entries::{KeyValGet, KeyValSet, KeyValNew};
use crate::algebra::matrices::query::MatrixOracle;
use crate::algebra::matrices::operations::umatch::row_major::Umatch;
use crate::algebra::rings::traits::{SemiringOperations, RingOperations, DivisionRingOperations};
use crate::algebra::rings::types::native::{RingOperatorForNativeRustNumberType, FieldFloat64};
use crate::utilities::order::{JudgePartialOrder, OrderOperatorByKeyCustom, IntoReverseOrder};
use crate::utilities::optimization::minimize_l1::SolutionL1;
use crate::algebra::matrices::operations::umatch::differential::DifferentialComb;
use crate::algebra::vectors::operations::VectorOperations;
use std::fmt::Debug;
use std::hash::Hash;
use std::collections::HashMap;
use derive_getters::{Getters, Dissolve};
use derive_new::new;
type RationalRing = RingOperatorForNativeRustNumberType< Ratio< isize > >;
pub fn minimize_l1<
Key,
ConstraintMatrix,
ConstraintVector,
ConstantVector,
ConstantVectorEntry,
ColumnIndexIterable,
CostVector,
Coefficient,
>
(
mut a: ConstraintMatrix,
b: ConstantVector,
mut c: CostVector,
column_indices: ColumnIndexIterable,
verbose: bool,
)
->
grb::Result< SolutionL1<Key, Coefficient> >
where
ConstraintMatrix: FnMut( & Key )->ConstraintVector,
ConstraintVector: IntoIterator,
ConstraintVector::Item: KeyValGet< Key = Key, Val = Coefficient >,
ConstantVector: IntoIterator< Item = ConstantVectorEntry >,
ConstantVectorEntry: 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 env: grb::Env;
let mut model: grb::Model;
if verbose {
env = Env::new("mip.log")?;
model = Model::new("model1")?;
model.set_param(grb::param::OutputFlag, 1)?;
} else {
let print_gag = Gag::stdout().unwrap(); env = Env::new("mip.log")?;
model = Model::new("model1")?;
model.set_param(grb::param::OutputFlag, 0)?;
}
let mut rowkey_to_rownum = HashMap::<Key,usize>::new();
let mut rownum_to_str: Vec<&str> = Vec::new();
let mut rownum_to_axz: Vec<LinExpr> = Vec::new();
let mut rownum_to_y = Vec::new();
let mut rownum_to_rowkey = Vec::new();
let mut cost_b = 0.0;
let mut b = b.into_iter()
.map( |entry| (entry.key(), entry.val() ) )
.collect_vec();
for entry in b.iter().cloned() {
let mut linexpr = LinExpr::new();
let coeff = entry.val().to_f64().unwrap();
linexpr.add_constant( coeff ); rownum_to_axz.push( linexpr );
cost_b += ( coeff * c( &entry.key() ) ).abs();
let variable_name= format!( "y{}", rownum_to_y.len() );
let variable = add_ctsvar!(model, name: &variable_name, bounds: ..)?;
rownum_to_y.push( variable );
rownum_to_rowkey.push( entry.key() );
rowkey_to_rownum.insert( entry.key(), rowkey_to_rownum.len() );
}
let mut x_kvpairs = Vec::new();
for column_index in column_indices.into_iter() {
let var_x_name= format!( "x{}", x_kvpairs.len() );
let var_x = add_ctsvar!(model, name: &var_x_name, bounds: ..)?;
let column = a( & column_index );
for entry in column.into_iter() {
let key = entry.key();
let coeff = entry.val().to_f64().unwrap();
if let Some(rownum) = rowkey_to_rownum.get( &key ) {
rownum_to_axz[rownum.clone()].add_term( coeff, var_x );
} else {
let mut linexpr = LinExpr::new();
linexpr.add_term( coeff, var_x );
rowkey_to_rownum.insert( key.clone(), rowkey_to_rownum.len() ); rownum_to_rowkey.push( key ); rownum_to_axz.push( linexpr );
let var_y_name= format!( "y{}", rownum_to_y.len() ); let var_y_self = add_ctsvar!(model, name: &var_y_name, bounds: ..)?; rownum_to_y.push( var_y_self ); }
}
x_kvpairs.push( ( column_index, var_x.clone() ) ); }
for (rownum, lhs) in rownum_to_axz.iter().cloned().enumerate() {
let y = rownum_to_y[ rownum ].clone();
let mut rhs = LinExpr::new();
rhs.add_term( 1.0, y );
let constraint_name_pos = format!( "p{}", rownum );
let constr = IneqExpr{
lhs: grb::Expr::Linear(lhs.clone()),
sense: ConstrSense::Less,
rhs: grb::Expr::Linear(rhs.clone()),
};
model.add_constr( &constraint_name_pos, constr );
let constraint_name_neg = format!( "n{}", rownum );
let mut lhs_neg = lhs.clone();
lhs_neg.mul_scalar( -1.0 );
let constr = IneqExpr{
lhs: grb::Expr::Linear(lhs_neg),
sense: ConstrSense::Less,
rhs: grb::Expr::Linear(rhs.clone()),
};
model.add_constr( &constraint_name_neg, constr );
}
let mut linexpr = LinExpr::new();
for (rowkey, var) in rownum_to_rowkey.iter().zip( rownum_to_y.iter().cloned() ) {
let cost_coefficient = c( rowkey );
match OrderedFloat(cost_coefficient) >= OrderedFloat(0f64) {
true => { linexpr.add_term( cost_coefficient, var ); }
false => { return Err( grb::Error::NotYetSupported("Error in OAT's optimization utility: the user provided a cost vector `c` with a negative entry; see the documentation for `minimize_l1` for further details".to_string()) ) }
}
}
model.set_objective( linexpr, Minimize );
let construction_duration = start.elapsed();
if verbose {
println!("Finished constructing L1 optimization program.");
println!("Construction took {:?} seconds.", construction_duration.as_secs_f64());
println!("Passing program to solver.\n");
}
let start = std::time::Instant::now();
model.write("model.lp");
model.optimize();
assert_eq!(model.status()?, Status::Optimal);
model.write("mip.lp")?;
model.write("mip.sol")?;
let solve_duration = start.elapsed();
if verbose {
println!("\nFinished solving.\n");
println!("Solver took {:?} seconds in walltime.", solve_duration.as_secs_f64());
}
let cost_axb = model.get_attr(attr::ObjVal)?;
let x
= x_kvpairs.iter().map(
| ( key, var ) |
(
key.clone(),
model
.get_obj_attr(attr::X, var)
.expect( &format!("Error retreiving the coefficient of `x` for key {:?}", key.clone()) )
)
)
.filter(|x| x.1 != 0.0 )
.collect_vec();
let y = rownum_to_axz.iter().enumerate().map(
|(rownum, linexpr)| -> (Key,f64)
{
let key = rownum_to_rowkey[rownum].clone();
(
key.clone(),
linexpr
.get_value(&model)
.expect( &format!("Error retreiving the coefficient of `x` for key {:?}", key.clone()) )
)
}
)
.filter( |x: &(Key,f64)| x.1 != 0.0 )
.collect_vec();
let answer = SolutionL1{
cost_b,
cost_y: cost_axb,
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_a() {
use crate::utilities::optimization::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 solution = minimize_l1( a.clone(),b.clone(),c.clone(), column_indices).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_axb(), &0.0 );
}
#[test]
fn test_l1_b() {
use crate::utilities::optimization::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 solution = minimize_l1( a.clone(),b.clone(),c.clone(), column_indices).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_axb(), &1.0 );
}
}