use std::collections::HashMap;
use nalgebra_sparse::CscMatrix;
use crate::canon::{ConeConstraint, LinExpr, QuadExpr};
use crate::expr::{ExprId, Shape};
use crate::sparse::{csc_from_triplets, csc_scale};
#[derive(Debug, Clone, Default)]
pub struct ConeDims {
pub zero: usize,
pub nonneg: usize,
pub soc: Vec<usize>,
pub exp: usize,
pub power: Vec<f64>,
}
impl ConeDims {
pub fn total(&self) -> usize {
self.zero
+ self.nonneg
+ self.soc.iter().sum::<usize>()
+ (self.exp * 3)
+ (self.power.len() * 3)
}
}
#[derive(Debug, Clone)]
pub struct VariableMap {
pub id_to_col: HashMap<ExprId, (usize, usize)>,
pub total_vars: usize,
}
impl VariableMap {
pub fn from_vars(vars: &[(ExprId, Shape)]) -> Self {
let mut id_to_col = HashMap::new();
let mut offset = 0;
for (var_id, shape) in vars {
let size = shape.size();
id_to_col.insert(*var_id, (offset, size));
offset += size;
}
VariableMap {
id_to_col,
total_vars: offset,
}
}
pub fn get(&self, var_id: ExprId) -> Option<(usize, usize)> {
self.id_to_col.get(&var_id).copied()
}
}
#[derive(Debug)]
pub struct StuffedProblem {
pub p: CscMatrix<f64>,
pub q: Vec<f64>,
pub a: CscMatrix<f64>,
pub b: Vec<f64>,
pub cone_dims: ConeDims,
pub var_map: VariableMap,
pub objective_offset: f64,
}
pub fn stuff_problem(
objective: &QuadExpr,
constraints: &[ConeConstraint],
variables: &[(ExprId, Shape)],
) -> StuffedProblem {
let var_map = VariableMap::from_vars(variables);
let (p, q) = stuff_objective(objective, &var_map);
let (a, b, cone_dims) = stuff_constraints(constraints, &var_map);
StuffedProblem {
p,
q,
a,
b,
cone_dims,
var_map,
objective_offset: objective.constant,
}
}
fn stuff_objective(objective: &QuadExpr, var_map: &VariableMap) -> (CscMatrix<f64>, Vec<f64>) {
let n = var_map.total_vars;
let mut q = vec![0.0; n];
for (var_id, coeff) in &objective.linear.coeffs {
if let Some((start, size)) = var_map.get(*var_id) {
for (_row, col, val) in coeff.triplet_iter() {
if col < size {
q[start + col] += *val;
}
}
}
}
let mut p_rows = Vec::new();
let mut p_cols = Vec::new();
let mut p_vals = Vec::new();
for ((var_i, var_j), coeff) in &objective.quad_coeffs {
if let (Some((start_i, _)), Some((start_j, _))) = (var_map.get(*var_i), var_map.get(*var_j))
{
for (row, col, val) in coeff.triplet_iter() {
let global_row = start_i + row;
let global_col = start_j + col;
if global_row <= global_col {
p_rows.push(global_row);
p_cols.push(global_col);
p_vals.push(*val);
}
}
}
}
let p = csc_from_triplets(n, n, p_rows, p_cols, p_vals);
let p_scaled = csc_scale(&p, 2.0);
(p_scaled, q)
}
fn stuff_constraints(
constraints: &[ConeConstraint],
var_map: &VariableMap,
) -> (CscMatrix<f64>, Vec<f64>, ConeDims) {
let n = var_map.total_vars;
let mut zeros: Vec<&LinExpr> = Vec::new();
let mut nonnegs: Vec<&LinExpr> = Vec::new();
let mut socs: Vec<(&LinExpr, &LinExpr)> = Vec::new(); let mut exps: Vec<(&LinExpr, &LinExpr, &LinExpr)> = Vec::new(); let mut powers: Vec<(&LinExpr, &LinExpr, &LinExpr, f64)> = Vec::new();
for c in constraints {
match c {
ConeConstraint::Zero { a } => zeros.push(a),
ConeConstraint::NonNeg { a } => nonnegs.push(a),
ConeConstraint::SOC { t, x } => socs.push((t, x)),
ConeConstraint::ExpCone { x, y, z } => exps.push((x, y, z)),
ConeConstraint::PowerCone { x, y, z, alpha } => powers.push((x, y, z, *alpha)),
}
}
let zero_rows: usize = zeros.iter().map(|e| e.size()).sum();
let nonneg_rows: usize = nonnegs.iter().map(|e| e.size()).sum();
let soc_dims: Vec<usize> = socs.iter().map(|(t, x)| t.size() + x.size()).collect();
let soc_rows: usize = soc_dims.iter().sum();
let exp_rows = exps.len() * 3; let power_alphas: Vec<f64> = powers.iter().map(|(_, _, _, alpha)| *alpha).collect();
let power_rows = powers.len() * 3;
let total_rows = zero_rows + nonneg_rows + soc_rows + exp_rows + power_rows;
let cone_dims = ConeDims {
zero: zero_rows,
nonneg: nonneg_rows,
soc: soc_dims,
exp: exps.len(),
power: power_alphas,
};
let mut a_rows = Vec::new();
let mut a_cols = Vec::new();
let mut a_vals = Vec::new();
let mut b = vec![0.0; total_rows];
let mut row_offset = 0;
for expr in zeros {
stuff_linear_expr(
expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
false, );
row_offset += expr.size();
}
for expr in nonnegs {
stuff_linear_expr(
expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += expr.size();
}
for (t_expr, x_expr) in socs {
stuff_linear_expr(
t_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += t_expr.size();
stuff_linear_expr(
x_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += x_expr.size();
}
for (x_expr, y_expr, z_expr) in exps {
stuff_linear_expr(
x_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += x_expr.size();
stuff_linear_expr(
y_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += y_expr.size();
stuff_linear_expr(
z_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += z_expr.size();
}
for (x_expr, y_expr, z_expr, _alpha) in powers {
stuff_linear_expr(
x_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += x_expr.size();
stuff_linear_expr(
y_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += y_expr.size();
stuff_linear_expr(
z_expr,
var_map,
row_offset,
&mut a_rows,
&mut a_cols,
&mut a_vals,
&mut b,
true, );
row_offset += z_expr.size();
}
let a = csc_from_triplets(total_rows, n, a_rows, a_cols, a_vals);
(a, b, cone_dims)
}
#[allow(clippy::too_many_arguments)]
fn stuff_linear_expr(
expr: &LinExpr,
var_map: &VariableMap,
row_offset: usize,
a_rows: &mut Vec<usize>,
a_cols: &mut Vec<usize>,
a_vals: &mut Vec<f64>,
b: &mut [f64],
negate: bool,
) {
let sign = if negate { -1.0 } else { 1.0 };
for (var_id, coeff) in &expr.coeffs {
if let Some((col_start, _)) = var_map.get(*var_id) {
for (row, col, val) in coeff.triplet_iter() {
a_rows.push(row_offset + row);
a_cols.push(col_start + col);
a_vals.push(*val * sign);
}
}
}
let size = expr.size();
for i in 0..expr.constant.nrows() {
for j in 0..expr.constant.ncols() {
let idx = i * expr.constant.ncols() + j;
if idx < size {
let const_val = expr.constant[(i, j)];
b[row_offset + idx] = if negate { const_val } else { -const_val };
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expr::ExprId;
#[test]
fn test_variable_map() {
let vars = vec![
(ExprId::new(), Shape::vector(3)),
(ExprId::new(), Shape::vector(2)),
];
let map = VariableMap::from_vars(&vars);
assert_eq!(map.total_vars, 5);
}
#[test]
fn test_cone_dims() {
let dims = ConeDims {
zero: 2,
nonneg: 3,
soc: vec![4, 5],
exp: 0,
power: vec![],
};
assert_eq!(dims.total(), 14);
}
#[test]
fn test_cone_dims_with_exp_power() {
let dims = ConeDims {
zero: 2,
nonneg: 3,
soc: vec![4, 5],
exp: 2, power: vec![0.5, 0.7], };
assert_eq!(dims.total(), 26);
}
}