use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use super::{BoundAccumulator, RowType};
use crate::lexer::{RawCoefficient, RawConstraint, RawObjective};
use crate::model::{ComparisonOp, VariableType};
pub(super) fn build_objectives<'input>(
objective_rows: &[&'input str],
coefficients: &HashMap<(&'input str, &'input str), f64>,
column_order: &[&'input str],
) -> Vec<RawObjective<'input>> {
debug_assert!(objective_rows.iter().all(|r| !r.is_empty()), "objective_rows must not contain empty row names");
if objective_rows.is_empty() {
return vec![RawObjective { name: Cow::Borrowed("__obj__"), coefficients: Vec::new(), byte_offset: None }];
}
let mut objectives = Vec::with_capacity(objective_rows.len());
for &obj_row in objective_rows {
let mut objective_coefficients = Vec::new();
for &var_name in column_order {
if let Some(&value) = coefficients.get(&(var_name, obj_row)) {
objective_coefficients.push(RawCoefficient { name: var_name, value });
}
}
objectives.push(RawObjective { name: Cow::Borrowed(obj_row), coefficients: objective_coefficients, byte_offset: None });
}
debug_assert!(objectives.len() == objective_rows.len(), "should produce one objective per N-row");
objectives
}
pub(super) fn build_constraints<'input>(
row_types: &HashMap<&'input str, RowType>,
row_order: &[&'input str],
coefficients: &HashMap<(&'input str, &'input str), f64>,
column_order: &[&'input str],
rhs_values: &HashMap<&'input str, f64>,
range_values: &HashMap<&'input str, f64>,
) -> Vec<RawConstraint<'input>> {
debug_assert!(row_order.iter().all(|r| row_types.contains_key(r)), "every row in row_order must have a type in row_types");
let mut constraints = Vec::with_capacity(row_order.len());
for &row_name in row_order {
let row_type = row_types.get(row_name).copied().expect("row_order entries must exist in row_types (validated by debug_assert)");
debug_assert!(row_type != RowType::N, "N-type rows should not appear in row_order");
let operator = match row_type {
RowType::L => ComparisonOp::LTE,
RowType::G => ComparisonOp::GTE,
RowType::E => ComparisonOp::EQ,
RowType::N => unreachable!("N-type rows filtered above"),
};
let mut row_coeffs = Vec::new();
for &var_name in column_order {
if let Some(&value) = coefficients.get(&(var_name, row_name)) {
row_coeffs.push(RawCoefficient { name: var_name, value });
}
}
let rhs = rhs_values.get(row_name).copied().unwrap_or(0.0);
if let Some(&range_val) = range_values.get(row_name) {
let (lower_rhs, upper_rhs) = match row_type {
RowType::G => (rhs, rhs + range_val.abs()),
RowType::L => (rhs - range_val.abs(), rhs),
RowType::E => {
if range_val >= 0.0 {
(rhs, rhs + range_val)
} else {
(rhs + range_val, rhs)
}
}
RowType::N => unreachable!("N-type rows filtered above"),
};
constraints.push(RawConstraint::Standard {
name: Cow::Borrowed(row_name),
coefficients: row_coeffs.clone(),
operator: ComparisonOp::GTE,
rhs: lower_rhs,
byte_offset: None,
});
constraints.push(RawConstraint::Standard {
name: Cow::Owned(format!("{row_name}_rng")),
coefficients: row_coeffs,
operator: ComparisonOp::LTE,
rhs: upper_rhs,
byte_offset: None,
});
} else {
constraints.push(RawConstraint::Standard {
name: Cow::Borrowed(row_name),
coefficients: row_coeffs,
operator,
rhs,
byte_offset: None,
});
}
}
debug_assert!(constraints.len() >= row_order.len(), "constraints cannot be fewer than rows (ranges add extra)");
constraints
}
pub(super) fn build_bounds<'input>(
bound_accumulators: &HashMap<&'input str, BoundAccumulator>,
bound_order: &[&'input str],
column_order: &[&'input str],
integer_vars: &HashSet<&'input str>,
) -> Vec<(&'input str, VariableType)> {
debug_assert!(bound_order.iter().all(|v| bound_accumulators.contains_key(v)), "every variable in bound_order must have an accumulator");
let mut bounds = Vec::with_capacity(bound_order.len() + column_order.len());
let mut has_explicit_bounds: HashSet<&str> = HashSet::with_capacity(bound_order.len());
for &var_name in bound_order {
has_explicit_bounds.insert(var_name);
let Some(accumulator) = bound_accumulators.get(var_name) else {
continue;
};
let is_integer = integer_vars.contains(var_name);
let var_type = if accumulator.binary {
VariableType::Binary
} else if accumulator.free {
VariableType::Free
} else if let Some(fixed) = accumulator.fixed {
VariableType::DoubleBound(fixed, fixed)
} else {
match (accumulator.lower, accumulator.upper) {
(Some(lo), Some(hi)) => {
if is_integer && lo == 0.0 && hi == 1.0 { VariableType::Binary } else { VariableType::DoubleBound(lo, hi) }
}
(Some(lo), None) => {
if is_integer && lo == 0.0 { VariableType::Integer } else { VariableType::LowerBound(lo) }
}
(None, Some(hi)) => {
if is_integer && hi == 1.0 {
VariableType::Binary
} else if hi < 0.0 {
VariableType::DoubleBound(f64::NEG_INFINITY, hi)
} else {
VariableType::UpperBound(hi)
}
}
(None, None) => continue, }
};
bounds.push((var_name, var_type));
}
for &var_name in column_order {
if has_explicit_bounds.contains(var_name) {
continue;
}
if integer_vars.contains(var_name) {
bounds.push((var_name, VariableType::DoubleBound(0.0, 1.0)));
} else {
bounds.push((var_name, VariableType::LowerBound(0.0)));
}
}
debug_assert!(
!bounds.is_empty() || (bound_order.is_empty() && column_order.is_empty()),
"bounds should be non-empty when there are variables"
);
bounds
}