use crate::basis::LuBasis;
const EPS: f64 = 1e-8;
pub(crate) trait PricingStrategy {
fn select_entering(&self, reduced_costs: &[f64], n_basic: usize) -> Option<usize>;
fn update_weights(&mut self, basis: &LuBasis, entering: usize, leaving: usize, eta: &[f64]);
}
#[cfg(test)]
pub(crate) struct DantzigPricing;
#[cfg(test)]
impl PricingStrategy for DantzigPricing {
fn select_entering(&self, reduced_costs: &[f64], n_basic: usize) -> Option<usize> {
let limit = n_basic.min(reduced_costs.len());
let mut min_rc = -EPS;
let mut entering = None;
for (j, &rc) in reduced_costs.iter().enumerate().take(limit) {
if rc < min_rc {
min_rc = rc;
entering = Some(j);
}
}
entering
}
fn update_weights(&mut self, _: &LuBasis, _: usize, _: usize, _: &[f64]) {}
}
pub(crate) struct SteepestEdgePricing {
weights: Vec<f64>,
}
impl SteepestEdgePricing {
pub fn new(n_vars: usize) -> Self {
Self {
weights: vec![1.0; n_vars],
}
}
}
impl PricingStrategy for SteepestEdgePricing {
fn select_entering(&self, reduced_costs: &[f64], n_basic: usize) -> Option<usize> {
let limit = n_basic.min(reduced_costs.len());
let mut best_score = -EPS;
let mut entering = None;
for (j, &rc) in reduced_costs.iter().enumerate().take(limit) {
if rc < -EPS {
let gamma = self.weights.get(j).copied().unwrap_or(1.0).max(1e-10);
let score = -rc / gamma.sqrt();
if score > best_score {
best_score = score;
entering = Some(j);
}
}
}
entering
}
fn update_weights(&mut self, _basis: &LuBasis, entering: usize, leaving: usize, eta: &[f64]) {
let gamma_entering = self
.weights
.get(entering)
.copied()
.unwrap_or(1.0)
.max(1e-10);
if leaving < self.weights.len() {
let eta_norm_sq: f64 = eta.iter().map(|&x| x * x).sum();
let new_weight = eta_norm_sq / gamma_entering;
self.weights[leaving] = self.weights[leaving].max(new_weight);
}
if entering < self.weights.len() {
self.weights[entering] = 1.0;
}
}
}
pub(crate) trait DualLeavingStrategy {
fn select_leaving(&mut self, x_b: &[f64], primal_tol: f64, basis: &[usize]) -> Option<usize>;
fn bland_leaving(&mut self, x_b: &[f64], primal_tol: f64, basis: &[usize]) -> Option<usize> {
let mut best_row: Option<usize> = None;
let mut best_var = usize::MAX;
for (i, &v) in x_b.iter().enumerate() {
if v < -primal_tol && basis[i] < best_var {
best_var = basis[i];
best_row = Some(i);
}
}
best_row
}
fn progress_metric(&mut self, x_b: &[f64], _basis: &[usize]) -> f64 {
x_b.iter().map(|&v| (-v).max(0.0)).sum()
}
fn needs_sigma(&self) -> bool {
false
}
fn after_pivot(
&mut self,
_leaving_row: usize,
_alpha: &[f64],
_sigma: &[f64],
_pivot: f64,
) {
}
fn after_refactor(&mut self, _m: usize) {}
fn set_initial_gamma(&mut self, _gamma_truth: &[f64]) {}
}
pub(crate) struct MostInfeasibleLeaving;
impl DualLeavingStrategy for MostInfeasibleLeaving {
fn select_leaving(&mut self, x_b: &[f64], primal_tol: f64, _basis: &[usize]) -> Option<usize> {
let mut best_row = None;
let mut max_violation = primal_tol;
for (i, &val) in x_b.iter().enumerate() {
if val < -max_violation {
max_violation = -val;
best_row = Some(i);
}
}
best_row
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dantzig_selects_most_negative() {
let pricing = DantzigPricing;
let rc = vec![0.0, -0.5, -1.0, 0.2, -0.3];
let entering = pricing.select_entering(&rc, rc.len());
assert_eq!(entering, Some(2)); }
#[test]
fn test_dantzig_returns_none_when_optimal() {
let pricing = DantzigPricing;
let rc = vec![0.0, 0.1, 0.5, 0.0];
assert_eq!(pricing.select_entering(&rc, rc.len()), None);
}
#[test]
fn test_dantzig_respects_n_basic_limit() {
let pricing = DantzigPricing;
let rc = vec![-0.1, -0.5, -1.0]; let entering = pricing.select_entering(&rc, 2);
assert_eq!(entering, Some(1)); }
#[test]
fn test_steepest_edge_scoring() {
let pricing = SteepestEdgePricing {
weights: vec![1.0, 4.0, 1.0], };
let rc = vec![-1.0, -2.0, -0.5];
let entering = pricing.select_entering(&rc, rc.len());
assert!(entering == Some(0) || entering == Some(1));
}
#[test]
fn test_steepest_edge_prefers_better_score() {
let pricing2 = SteepestEdgePricing {
weights: vec![1.0, 100.0, 1.0],
};
let rc = vec![-1.0, -10.0, -0.5];
let entering = pricing2.select_entering(&rc, rc.len());
assert!(entering == Some(0) || entering == Some(1));
}
#[test]
fn test_weight_update_resets_entering() {
let mut pricing = SteepestEdgePricing::new(5);
pricing.weights[2] = 3.5;
assert_eq!(pricing.weights[2], 3.5);
}
}