use std::collections::HashMap;
use std::hash::{BuildHasher, Hash};
use oximo_core::VarId;
use rustc_hash::FxHashMap;
use crate::{DualStatus, PrimalStatus, SolverResult, TerminationStatus};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DualProjection<K> {
pub source: K,
pub scale: f64,
}
impl<K: Copy + Eq + Hash> DualProjection<K> {
pub fn accumulate<S: BuildHasher>(
self,
map: &mut HashMap<K, f64, S>,
value: f64,
objective_sign: f64,
) {
accumulate_dual(map, self.source, value, self.scale * objective_sign);
}
}
#[derive(Clone, Copy, Debug)]
pub struct ObjectiveTransform {
pub sign: f64,
pub offset: f64,
}
impl ObjectiveTransform {
pub fn restore(self, value: f64) -> Option<f64> {
let mapped = self.sign * value + self.offset;
(value.is_finite() && mapped.is_finite()).then_some(mapped)
}
}
pub fn project_primal(
values: &[f64],
columns: &[Option<VarId>],
num_variables: usize,
) -> Option<FxHashMap<VarId, f64>> {
if values.len() != columns.len() {
return None;
}
let mut primal = FxHashMap::default();
for (&value, &column) in values.iter().zip(columns) {
if let Some(id) = column
&& (id.index() >= num_variables
|| !value.is_finite()
|| primal.insert(id, value).is_some())
{
return None;
}
}
(primal.len() == num_variables).then_some(primal)
}
pub fn project_dense_primal(values: &[f64], num_variables: usize) -> Option<FxHashMap<VarId, f64>> {
if values.len() != num_variables {
return None;
}
values
.iter()
.enumerate()
.map(|(index, &value)| {
let id = VarId(u32::try_from(index).ok()?);
value.is_finite().then_some((id, value))
})
.collect()
}
pub fn accumulate_dual<K: Eq + Hash, S: BuildHasher>(
map: &mut HashMap<K, f64, S>,
id: K,
value: f64,
factor: f64,
) {
*map.entry(id).or_insert(0.0) += value * factor;
}
pub fn relative_gap(primal: Option<f64>, bound: Option<f64>) -> Option<f64> {
let (p, b) = (primal?, bound?);
if !p.is_finite() || !b.is_finite() {
return None;
}
let scale = p.abs().max(b.abs()) + 1e-10;
Some((p / scale - b / scale).abs())
}
pub fn normalize_result(mut result: SolverResult, num_variables: usize) -> SolverResult {
let model_id = result.model_id;
for point in &mut result.solutions {
point.model_id = model_id;
}
let mut first = true;
let mut lost_incumbent = false;
result.solutions.retain_mut(|point| {
point.primal.retain(|id, value| id.index() < num_variables && value.is_finite());
point.objective = point.objective.filter(|v| v.is_finite());
let valid = point.primal.len() == num_variables;
if first {
lost_incumbent = !valid;
first = false;
}
valid
});
let optimal_point = result.termination == TerminationStatus::Optimal
&& !lost_incumbent
&& result.primal_status != PrimalStatus::FeasiblePoint;
result.primal_status = if result.solutions.is_empty() {
PrimalStatus::NoSolution
} else if optimal_point {
PrimalStatus::OptimalPoint
} else {
PrimalStatus::FeasiblePoint
};
if lost_incumbent {
result.dual_status = DualStatus::Unknown;
result.gap = None;
}
if result.dual_status != DualStatus::FeasiblePoint {
result.dual.clear();
result.soc_dual.clear();
result.reduced_costs.clear();
}
result.dual.retain(|_, v| v.is_finite());
result.soc_dual.retain(|_, v| v.is_finite() && *v >= 0.0);
result.reduced_costs.retain(|_, v| v.is_finite());
result.best_bound = result.best_bound.filter(|v| v.is_finite());
if result.primal_status == PrimalStatus::OptimalPoint && result.best_bound.is_none() {
result.best_bound = result.objective();
}
result.gap = result.gap.filter(|v| v.is_finite() && *v >= 0.0);
if result.primal_status == PrimalStatus::NoSolution {
result.gap = None;
}
result
}