oximo_solver/
reconstruct.rs1use std::collections::HashMap;
7use std::hash::{BuildHasher, Hash};
8
9use oximo_core::VarId;
10use rustc_hash::FxHashMap;
11
12use crate::{DualStatus, PrimalStatus, SolverResult, TerminationStatus};
13
14#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct DualProjection<K> {
18 pub source: K,
19 pub scale: f64,
20}
21
22impl<K: Copy + Eq + Hash> DualProjection<K> {
23 pub fn accumulate<S: BuildHasher>(
24 self,
25 map: &mut HashMap<K, f64, S>,
26 value: f64,
27 objective_sign: f64,
28 ) {
29 accumulate_dual(map, self.source, value, self.scale * objective_sign);
30 }
31}
32
33#[derive(Clone, Copy, Debug)]
36pub struct ObjectiveTransform {
37 pub sign: f64,
38 pub offset: f64,
39}
40
41impl ObjectiveTransform {
42 pub fn restore(self, value: f64) -> Option<f64> {
43 let mapped = self.sign * value + self.offset;
44 (value.is_finite() && mapped.is_finite()).then_some(mapped)
45 }
46}
47
48pub fn project_primal(
51 values: &[f64],
52 columns: &[Option<VarId>],
53 num_variables: usize,
54) -> Option<FxHashMap<VarId, f64>> {
55 if values.len() != columns.len() {
56 return None;
57 }
58 let mut primal = FxHashMap::default();
59 for (&value, &column) in values.iter().zip(columns) {
60 if let Some(id) = column
61 && (id.index() >= num_variables
62 || !value.is_finite()
63 || primal.insert(id, value).is_some())
64 {
65 return None;
66 }
67 }
68 (primal.len() == num_variables).then_some(primal)
69}
70
71pub fn project_dense_primal(values: &[f64], num_variables: usize) -> Option<FxHashMap<VarId, f64>> {
73 if values.len() != num_variables {
74 return None;
75 }
76 values
77 .iter()
78 .enumerate()
79 .map(|(index, &value)| {
80 let id = VarId(u32::try_from(index).ok()?);
81 value.is_finite().then_some((id, value))
82 })
83 .collect()
84}
85
86pub fn accumulate_dual<K: Eq + Hash, S: BuildHasher>(
89 map: &mut HashMap<K, f64, S>,
90 id: K,
91 value: f64,
92 factor: f64,
93) {
94 *map.entry(id).or_insert(0.0) += value * factor;
95}
96
97pub fn relative_gap(primal: Option<f64>, bound: Option<f64>) -> Option<f64> {
99 let (p, b) = (primal?, bound?);
100 if !p.is_finite() || !b.is_finite() {
101 return None;
102 }
103 let scale = p.abs().max(b.abs()) + 1e-10;
104 Some((p / scale - b / scale).abs())
105}
106
107pub fn normalize_result(mut result: SolverResult, num_variables: usize) -> SolverResult {
114 let model_id = result.model_id;
115 for point in &mut result.solutions {
116 point.model_id = model_id;
117 }
118 let mut first = true;
119 let mut lost_incumbent = false;
120 result.solutions.retain_mut(|point| {
121 point.primal.retain(|id, value| id.index() < num_variables && value.is_finite());
122 point.objective = point.objective.filter(|v| v.is_finite());
123 let valid = point.primal.len() == num_variables;
124 if first {
125 lost_incumbent = !valid;
126 first = false;
127 }
128 valid
129 });
130 let optimal_point = result.termination == TerminationStatus::Optimal
134 && !lost_incumbent
135 && result.primal_status != PrimalStatus::FeasiblePoint;
136 result.primal_status = if result.solutions.is_empty() {
137 PrimalStatus::NoSolution
138 } else if optimal_point {
139 PrimalStatus::OptimalPoint
140 } else {
141 PrimalStatus::FeasiblePoint
142 };
143 if lost_incumbent {
144 result.dual_status = DualStatus::Unknown;
145 result.gap = None;
146 }
147 if result.dual_status != DualStatus::FeasiblePoint {
148 result.dual.clear();
149 result.soc_dual.clear();
150 result.reduced_costs.clear();
151 }
152 result.dual.retain(|_, v| v.is_finite());
153 result.soc_dual.retain(|_, v| v.is_finite() && *v >= 0.0);
154 result.reduced_costs.retain(|_, v| v.is_finite());
155 result.best_bound = result.best_bound.filter(|v| v.is_finite());
156 if result.primal_status == PrimalStatus::OptimalPoint && result.best_bound.is_none() {
157 result.best_bound = result.objective();
158 }
159 result.gap = result.gap.filter(|v| v.is_finite() && *v >= 0.0);
160 if result.primal_status == PrimalStatus::NoSolution {
161 result.gap = None;
162 }
163 result
164}