use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use relp_num::{OrderedField, OrderedFieldRef};
use crate::data::linear_algebra::traits::SparseElement;
use crate::data::linear_program::elements::{BoundDirection, LinearProgramType, Objective, RangedConstraintRelation};
use crate::data::linear_program::general_form::{GeneralForm, RemovedVariable};
use crate::data::linear_program::general_form::presolve::counters::Counters;
use crate::data::linear_program::general_form::presolve::is_empty_constraint_feasible;
pub struct Updates<'a, F> {
pub b: HashMap<usize, F>,
pub constraints: HashMap<usize, RangedConstraintRelation<F>>,
pub fixed_cost: F,
pub bounds: HashMap<(usize, BoundDirection), F>,
pub removed_variables: Vec<(usize, RemovedVariable<F>)>,
pub constraints_marked_removed: Vec<usize>,
pub activity_bounds: HashMap<(usize, BoundDirection), F>,
general_form: &'a GeneralForm<F>,
}
#[derive(Eq, PartialEq)]
pub (super) enum BoundChange<F> {
None,
NewBound,
BoundShift(F),
}
impl<'a, OF> Updates<'a, OF>
where
OF: OrderedField + SparseElement<OF>,
for<'r> &'r OF: OrderedFieldRef<OF>,
{
pub(super) fn new(
general_form: &'a GeneralForm<OF>,
counters: &Counters<OF>,
) -> Result<Self, LinearProgramType<OF>> {
let mut fixed_cost = OF::zero();
let removed_variables = counters.variable.iter().enumerate()
.filter(|&(_, &count)| count == 0)
.map(|(j, _)| {
let variable = &general_form.variables[j];
debug_assert!(variable.has_feasible_value());
let value = if variable.cost.is_zero() {
variable.get_feasible_value().unwrap()
} else {
let value = optimize_independent_column(
general_form.objective,
&variable.cost,
(variable.lower_bound.as_ref(), variable.upper_bound.as_ref()),
)?;
fixed_cost += &variable.cost * &value;
value
};
Ok((j, RemovedVariable::Solved(value)))
}).collect::<Result<_, _>>()?;
let constraints_marked_removed = counters.constraint.iter().enumerate()
.filter(|&(_, &count)| count == 0)
.map(|(constraint, _)| {
let right_hand_side = &general_form.b[constraint];
let constraint_type = &general_form.constraint_types[constraint];
if is_empty_constraint_feasible(right_hand_side, constraint_type) {
Ok(constraint)
} else {
Err(LinearProgramType::Infeasible)
}
})
.collect::<Result<_, _>>()?;
Ok(Self {
b: HashMap::new(),
constraints: HashMap::new(),
fixed_cost,
bounds: HashMap::new(),
removed_variables,
constraints_marked_removed,
activity_bounds: HashMap::new(),
general_form,
})
}
pub(super) fn b(&self, constraint: usize) -> &OF {
debug_assert!(!self.constraints_marked_removed.contains(&constraint));
self.b.get(&constraint).unwrap_or(&self.general_form.b[constraint])
}
pub(super) fn change_b(&mut self, constraint: usize, change: OF) {
debug_assert!(!self.constraints_marked_removed.contains(&constraint));
if let Some(existing_value) = self.b.get_mut(&constraint) {
*existing_value += change;
} else {
self.b.insert(constraint, &self.general_form.b[constraint] + change);
}
}
pub(super) fn constraint_type(&self, constraint: usize) -> &RangedConstraintRelation<OF> {
debug_assert!(!self.constraints_marked_removed.contains(&constraint));
self.constraints.get(&constraint)
.unwrap_or(&self.general_form.constraint_types[constraint])
}
pub(super) fn is_variable_fixed(&self, variable: usize) -> Option<&OF> {
debug_assert!(!self.removed_variables.iter().any(|&(j, _)| j == variable));
let lower = self.variable_bound(variable, BoundDirection::Lower);
let upper = self.variable_bound(variable, BoundDirection::Upper);
match (lower, upper) {
(Some(lower), Some(upper)) if lower == upper => Some(lower),
_ => None,
}
}
pub(super) fn variable_feasible_value(&self, variable: usize) -> Option<OF> {
debug_assert!(!self.removed_variables.iter().any(|&(j, _)| j == variable));
let lower = self.variable_bound(variable, BoundDirection::Lower);
let upper = self.variable_bound(variable, BoundDirection::Upper);
match (lower, upper) {
(None, None) => Some(OF::zero()),
(None, Some(bound)) => Some(bound.clone()),
(Some(bound), None) => Some(bound.clone()),
(Some(lower), Some(upper)) => {
if lower <= upper {
Some(upper.clone())
} else {
None
}
}
}
}
pub(super) fn variable_bound(&self, variable: usize, direction: BoundDirection) -> Option<&OF> {
debug_assert!(self.removed_variables.iter().all(|&(j, _)| j != variable));
debug_assert!({
let certain = self.bounds.get(&(variable, direction));
let activity = self.activity_bounds.get(&(variable, direction));
!matches!((certain, activity), (Some(_), Some(_)))
});
self.activity_bounds.get(&(variable, direction))
.or_else(|| self.bounds.get(&(variable, direction)))
.or_else(|| {
let variable = &self.general_form.variables[variable];
match direction {
BoundDirection::Lower => &variable.lower_bound,
BoundDirection::Upper => &variable.upper_bound,
}.as_ref()
})
}
pub(super) fn update_bound(
&mut self,
variable: usize,
direction: BoundDirection,
new: OF,
) -> BoundChange<OF> {
debug_assert!(self.removed_variables.iter().all(|&(j, _)| j != variable));
let compare_with = match self.bounds.get(&(variable, direction)) {
None => {
match self.activity_bounds.remove(&(variable, direction)) {
None => {
let original = &self.general_form.variables[variable];
let original = match direction {
BoundDirection::Lower => &original.lower_bound,
BoundDirection::Upper => &original.upper_bound,
};
match original {
None => {
self.bounds.insert((variable, direction), new);
return BoundChange::NewBound
},
Some(original) => original,
}
},
Some(existing_activity) => {
self.bounds.insert((variable, direction), existing_activity);
self.bounds.get(&(variable, direction)).unwrap()
}
}
},
Some(existing) => existing,
};
let compare_with = compare_with.clone();
bound_compare_and_update(variable, direction, new, &compare_with, &mut self.bounds)
}
pub(super) fn update_activity_variable_bound(
&mut self,
variable: usize,
direction: BoundDirection,
new: OF,
) -> BoundChange<OF> {
debug_assert!(self.removed_variables.iter().all(|&(j, _)| j != variable));
match self.activity_bounds.get(&(variable, direction)) {
None => match self.bounds.get(&(variable, direction)) {
None => {
let original = &self.general_form.variables[variable];
let bound = match direction {
BoundDirection::Lower => &original.lower_bound,
BoundDirection::Upper => &original.upper_bound,
};
match bound {
None => {
self.activity_bounds.insert((variable, direction), new);
BoundChange::NewBound
},
Some(original) => bound_compare_and_update(
variable, direction, new,
original,
&mut self.activity_bounds,
),
}
},
Some(derived) => bound_compare_and_update(
variable, direction, new,
&derived.clone(),
&mut self.bounds,
),
},
Some(activity_derived) => bound_compare_and_update(
variable, direction, new,
&activity_derived.clone(),
&mut self.activity_bounds,
),
}
}
pub(super) fn optimize_column_independently(
&mut self,
variable: usize,
) -> Result<RemovedVariable<OF>, LinearProgramType<OF>> {
debug_assert_ne!(self.general_form.variables[variable].cost, OF::zero());
let new_value = self.get_optimized_value(variable)?;
self.fixed_cost += &self.general_form.variables[variable].cost * &new_value;
Ok(RemovedVariable::Solved(new_value))
}
fn get_optimized_value(&self, variable: usize) -> Result<OF, LinearProgramType<OF>> {
let objective = self.general_form.objective;
let cost = &self.general_form.variables[variable].cost;
let lower_bound = self.variable_bound(variable, BoundDirection::Lower);
let upper_bound = self.variable_bound(variable, BoundDirection::Upper);
optimize_independent_column(objective, cost, (lower_bound, upper_bound))
}
pub fn into_changes(mut self) -> Changes<OF> {
for constraint in &self.constraints_marked_removed {
self.b.remove(constraint);
self.constraints.remove(constraint);
}
for variable in self.removed_variables.iter().map(|&(j, _)| j) {
self.bounds.remove(&(variable, BoundDirection::Lower));
self.bounds.remove(&(variable, BoundDirection::Upper));
self.activity_bounds.remove(&(variable, BoundDirection::Lower));
self.activity_bounds.remove(&(variable, BoundDirection::Upper));
}
let free_to_be_restricted = self.activity_bounds.iter()
.map(|(&(variable, _), _)| variable)
.filter(|&variable| {
self.general_form.variables[variable].is_free() &&
self.bounds.get(&(variable, BoundDirection::Lower)).is_none() &&
self.bounds.get(&(variable, BoundDirection::Upper)).is_none()
})
.collect::<HashSet<_>>();
for ((variable, direction), bound_value) in self.activity_bounds {
if free_to_be_restricted.contains(&variable) {
self.bounds.insert((variable, direction), bound_value);
}
}
let existing_b = &self.general_form.b;
let b = self.b.into_iter()
.filter(|&(i, ref new_b)| new_b != &existing_b[i])
.collect();
let existing_constraint_types = &self.general_form.constraint_types;
let constraints = self.constraints.into_iter()
.filter(|(constraint, constraint_type)| {
&existing_constraint_types[*constraint] != constraint_type
})
.collect();
self.removed_variables.sort_unstable_by_key(|&(j, _)| j);
self.constraints_marked_removed.sort_unstable();
Changes {
b,
constraints,
fixed_cost: self.fixed_cost,
bounds: self.bounds,
removed_variables: self.removed_variables,
constraints_marked_removed: self.constraints_marked_removed,
}
}
pub fn nr_variables_remaining(&self) -> usize {
self.general_form.nr_active_variables() - self.removed_variables.len()
}
pub fn nr_constraints_remaining(&self) -> usize {
self.general_form.nr_active_constraints() - self.constraints_marked_removed.len()
}
}
#[derive(Eq, PartialEq, Debug)]
pub struct Changes<F> {
pub b: HashMap<usize, F>,
pub constraints: Vec<(usize, RangedConstraintRelation<F>)>,
pub fixed_cost: F,
pub bounds: HashMap<(usize, BoundDirection), F>,
pub removed_variables: Vec<(usize, RemovedVariable<F>)>,
pub constraints_marked_removed: Vec<usize>,
}
fn bound_compare_and_update<OF>(
variable: usize,
direction: BoundDirection,
new: OF,
existing: &OF,
bounds: &mut HashMap<(usize, BoundDirection), OF>,
) -> BoundChange<OF>
where
OF: OrderedField,
for<'r> &'r OF: OrderedFieldRef<OF>,
{
if match direction {
BoundDirection::Lower => &new > existing,
BoundDirection::Upper => &new < existing,
} {
let difference = &new - existing;
bounds.insert((variable, direction), new);
BoundChange::BoundShift(difference)
} else {
BoundChange::None
}
}
fn optimize_independent_column<OF>(
objective: Objective,
cost: &OF,
(lower_bound, upper_bound): (Option<&OF>, Option<&OF>),
) -> Result<OF, LinearProgramType<OF>>
where
OF: OrderedField,
for<'r> &'r OF: OrderedFieldRef<OF>,
{
let cost_sign = cost.cmp(&OF::zero());
match (objective, cost_sign) {
(Objective::Minimize, Ordering::Greater) | (Objective::Maximize, Ordering::Less) => {
lower_bound.ok_or(LinearProgramType::Unbounded)
},
(Objective::Maximize, Ordering::Greater) | (Objective::Minimize, Ordering::Less) => {
upper_bound.ok_or(LinearProgramType::Unbounded)
},
(_, Ordering::Equal) => panic!("Should not be called if there is no cost"),
}.map(OF::clone)
}