pub use cobre_solver::ffi::{HIGHS_BASIS_STATUS_BASIC, HIGHS_BASIS_STATUS_LOWER};
use cobre_solver::Basis;
use crate::cut_selection::CutMetadata;
use crate::workspace::CapturedBasis;
pub const DEFAULT_BASIS_ACTIVITY_WINDOW: u32 = 5;
pub const DEFAULT_RECENT_WINDOW_BITS: u32 = (1u32 << DEFAULT_BASIS_ACTIVITY_WINDOW) - 1;
pub const SEED_BIT: u32 = 1u32 << 31;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ReconstructionTarget {
pub base_row_count: usize,
pub num_cols: usize,
}
#[derive(Clone, Copy, Debug)]
pub struct ReconstructionSource<'a> {
pub target: ReconstructionTarget,
pub cut_metadata: &'a [CutMetadata],
pub basis_activity_window: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct PaddingContext<'a> {
pub state: &'a [f64],
pub theta: f64,
pub tolerance: f64,
}
#[derive(Debug, Default)]
pub struct PromotionScratch {
pub candidates: Vec<(usize, u32, u64, u32)>,
pub new_lower_indices: Vec<usize>,
}
impl PromotionScratch {
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
candidates: Vec::with_capacity(cap),
new_lower_indices: Vec::with_capacity(cap),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ReconstructionStats {
pub preserved: u32,
pub new_tight: u32,
pub new_slack: u32,
}
pub fn reconstruct_basis<'a, I>(
stored: &CapturedBasis,
source: ReconstructionSource<'_>,
current_cut_rows: I,
padding: PaddingContext<'_>,
out: &mut Basis,
slot_lookup: &mut Vec<Option<u32>>,
promotion_scratch: &mut PromotionScratch,
) -> ReconstructionStats
where
I: Iterator<Item = (usize, f64, &'a [f64])>,
{
let target = source.target;
let cut_metadata = source.cut_metadata;
let basis_activity_window = source.basis_activity_window;
debug_assert!(
(1..=31).contains(&basis_activity_window),
"basis_activity_window must be 1..=31 (validated upstream at StudyParams); got {basis_activity_window}",
);
let recent_window_bits: u32 = (1u32 << basis_activity_window) - 1;
promotion_scratch.candidates.clear();
promotion_scratch.new_lower_indices.clear();
debug_assert!(
padding.state.len() == stored.state_at_capture.len() || stored.state_at_capture.is_empty(),
"padding.state.len() {} != stored.state_at_capture.len() {}",
padding.state.len(),
stored.state_at_capture.len(),
);
if !stored.cut_row_slots.is_empty() {
debug_assert!(
stored.basis.row_status.len() == stored.base_row_count + stored.cut_row_slots.len(),
"CapturedBasis invariant violated: row_status.len() {} != base_row_count {} + \
cut_row_slots.len() {}",
stored.basis.row_status.len(),
stored.base_row_count,
stored.cut_row_slots.len(),
);
}
let reconcilable_slots = stored.cut_row_slots.as_slice();
reconstruct_col_statuses(stored, target, out);
reconstruct_template_row_statuses(stored, target, out);
build_slot_lookup(reconcilable_slots, slot_lookup);
let mut stats = ReconstructionStats::default();
let lower_deficit = classify_cut_rows(
current_cut_rows,
stored,
target,
cut_metadata,
slot_lookup,
recent_window_bits,
promotion_scratch,
out,
&mut stats,
);
apply_promotion_and_tail_fallback(lower_deficit, promotion_scratch, target, out, &mut stats);
stats
}
fn reconstruct_col_statuses(stored: &CapturedBasis, target: ReconstructionTarget, out: &mut Basis) {
out.col_status.clear();
out.col_status.extend_from_slice(&stored.basis.col_status);
if out.col_status.len() != target.num_cols {
out.col_status
.resize(target.num_cols, HIGHS_BASIS_STATUS_BASIC);
}
}
fn reconstruct_template_row_statuses(
stored: &CapturedBasis,
target: ReconstructionTarget,
out: &mut Basis,
) {
out.row_status.clear();
if stored.basis.row_status.len() >= target.base_row_count {
out.row_status
.extend_from_slice(&stored.basis.row_status[..target.base_row_count]);
} else {
out.row_status.extend_from_slice(&stored.basis.row_status);
out.row_status
.resize(target.base_row_count, HIGHS_BASIS_STATUS_BASIC);
}
}
fn build_slot_lookup(reconcilable_slots: &[u32], slot_lookup: &mut Vec<Option<u32>>) {
if let Some(max_slot_val) = reconcilable_slots.iter().copied().max() {
let max_slot = max_slot_val as usize;
if slot_lookup.len() <= max_slot {
debug_assert!(
false,
"slot_lookup undersized ({} <= max_slot {}); caller must pre-size to \
initial_pool_capacity",
slot_lookup.len(),
max_slot,
);
slot_lookup.resize(max_slot + 1, None);
}
}
slot_lookup.fill(None);
#[allow(clippy::cast_possible_truncation)]
for (position, &slot) in reconcilable_slots.iter().enumerate() {
slot_lookup[slot as usize] = Some(position as u32);
}
}
fn classify_cut_rows<'a, I>(
current_cut_rows: I,
stored: &CapturedBasis,
target: ReconstructionTarget,
cut_metadata: &[CutMetadata],
slot_lookup: &[Option<u32>],
recent_window_bits: u32,
promotion_scratch: &mut PromotionScratch,
out: &mut Basis,
stats: &mut ReconstructionStats,
) -> usize
where
I: Iterator<Item = (usize, f64, &'a [f64])>,
{
let mut lower_deficit: usize = 0;
for (target_slot, _intercept, _coefficients) in current_cut_rows {
let out_row_index = out.row_status.len();
debug_assert!(
out_row_index >= target.base_row_count,
"cut row index {out_row_index} must be >= base_row_count {}",
target.base_row_count,
);
let row_status_byte = if let Some(pos) = slot_lookup.get(target_slot).and_then(|o| *o) {
let stored_row_idx = stored.base_row_count + pos as usize;
let stored_status = stored.basis.row_status[stored_row_idx];
stats.preserved += 1;
if stored_status == HIGHS_BASIS_STATUS_LOWER {
let (popcount, last_active) = cut_metadata
.get(target_slot)
.map_or((0u32, 0u64), |m| {
(
(m.active_window & recent_window_bits).count_ones(),
m.last_active_iter,
)
});
#[allow(clippy::cast_possible_truncation)]
let idx = promotion_scratch.candidates.len() as u32;
promotion_scratch
.candidates
.push((out_row_index, popcount, last_active, idx));
}
stored_status
} else {
let active_window = cut_metadata.get(target_slot).map_or(0, |m| m.active_window);
if active_window & (recent_window_bits | SEED_BIT) != 0 {
stats.new_tight += 1;
lower_deficit += 1;
promotion_scratch.new_lower_indices.push(out_row_index);
HIGHS_BASIS_STATUS_LOWER
} else {
stats.new_slack += 1;
HIGHS_BASIS_STATUS_BASIC
}
};
out.row_status.push(row_status_byte);
}
lower_deficit
}
fn apply_promotion_and_tail_fallback(
lower_deficit: usize,
promotion_scratch: &mut PromotionScratch,
target: ReconstructionTarget,
out: &mut Basis,
stats: &mut ReconstructionStats,
) {
if lower_deficit == 0 {
return;
}
let available_candidates = promotion_scratch.candidates.len();
let scheme1_count = lower_deficit.min(available_candidates);
if scheme1_count > 0 && scheme1_count < available_candidates {
promotion_scratch
.candidates
.select_nth_unstable_by_key(scheme1_count - 1, |&(_, popcount, last_active, idx)| {
(popcount, last_active, idx)
});
}
for &(row_idx, _, _, _) in promotion_scratch.candidates.iter().take(scheme1_count) {
debug_assert!(
row_idx >= target.base_row_count,
"Scheme 1 promotion target row_idx {row_idx} must be >= base_row_count {}",
target.base_row_count,
);
debug_assert_eq!(
out.row_status[row_idx], HIGHS_BASIS_STATUS_LOWER,
"Scheme 1 promotion target must currently be LOWER",
);
out.row_status[row_idx] = HIGHS_BASIS_STATUS_BASIC;
}
let remaining_deficit = lower_deficit - scheme1_count;
if remaining_deficit > 0 {
for &row_idx in promotion_scratch
.new_lower_indices
.iter()
.rev()
.take(remaining_deficit)
{
debug_assert_eq!(
out.row_status[row_idx], HIGHS_BASIS_STATUS_LOWER,
"Scheme 2 override target must be LOWER",
);
out.row_status[row_idx] = HIGHS_BASIS_STATUS_BASIC;
}
#[allow(clippy::cast_possible_truncation)]
let n = remaining_deficit as u32;
stats.new_tight -= n;
stats.new_slack += n;
}
}
pub fn enforce_basic_count_invariant(
out: &mut Basis,
num_row: usize,
base_row_count: usize,
) -> u32 {
debug_assert_eq!(
num_row,
out.row_status.len(),
"enforce_basic_count_invariant: num_row ({num_row}) != out.row_status.len() ({})",
out.row_status.len(),
);
debug_assert!(
base_row_count <= num_row,
"enforce_basic_count_invariant: base_row_count ({base_row_count}) > num_row ({num_row})",
);
let col_basic = out
.col_status
.iter()
.filter(|&&s| s == HIGHS_BASIS_STATUS_BASIC)
.count();
let row_basic = out
.row_status
.iter()
.filter(|&&s| s == HIGHS_BASIS_STATUS_BASIC)
.count();
let total_basic = col_basic + row_basic;
if total_basic <= num_row {
return 0;
}
let mut excess = total_basic - num_row;
let mut demotions: u32 = 0;
for idx in (base_row_count..out.row_status.len()).rev() {
if excess == 0 {
break;
}
if out.row_status[idx] == HIGHS_BASIS_STATUS_BASIC {
out.row_status[idx] = HIGHS_BASIS_STATUS_LOWER;
excess -= 1;
demotions += 1;
}
}
demotions
}
#[cfg(test)]
#[allow(clippy::doc_markdown)]
mod tests {
use cobre_solver::Basis;
use super::{
DEFAULT_BASIS_ACTIVITY_WINDOW, HIGHS_BASIS_STATUS_BASIC as B,
HIGHS_BASIS_STATUS_LOWER as L, PaddingContext, PromotionScratch, ReconstructionSource,
ReconstructionStats, ReconstructionTarget, SEED_BIT, enforce_basic_count_invariant,
reconstruct_basis,
};
use crate::cut::pool::CutPool;
use crate::cut_selection::CutMetadata;
use crate::workspace::CapturedBasis;
fn make_stored_basis(
base_rows: usize,
num_cols: usize,
slots: &[u32],
cut_statuses: &[i32],
state_at_capture: &[f64],
) -> CapturedBasis {
assert_eq!(slots.len(), cut_statuses.len());
let total_rows = base_rows + cut_statuses.len();
let mut cb = CapturedBasis::new(
num_cols,
total_rows,
base_rows,
slots.len(),
state_at_capture.len(),
);
cb.basis.row_status.clear();
cb.basis.row_status.resize(base_rows, B);
cb.basis.row_status.extend_from_slice(cut_statuses);
cb.basis.col_status.clear();
cb.basis.col_status.resize(num_cols, B);
cb.cut_row_slots.extend_from_slice(slots);
cb.state_at_capture.extend_from_slice(state_at_capture);
cb
}
fn source_no_metadata(base_row_count: usize, num_cols: usize) -> ReconstructionSource<'static> {
ReconstructionSource {
target: ReconstructionTarget {
base_row_count,
num_cols,
},
cut_metadata: &[],
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
}
}
fn meta_with_window(active_window: u32) -> CutMetadata {
CutMetadata {
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
last_active_iter: 0,
active_window,
}
}
#[test]
fn test_empty_stored_all_new_cuts() {
let stored = CapturedBasis::new(3, 2, 2, 0, 0);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(5, 3.0, vec![1.0, 2.0]),
(6, 8.0, vec![1.0, 0.5]),
(7, 0.0, vec![0.0, 0.0]),
];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let stats = reconstruct_basis(
&stored,
source_no_metadata(2, 3),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 10.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 0,
new_tight: 0,
new_slack: 3
}
);
assert_eq!(out.row_status.len(), 5);
assert_eq!(out.row_status[2], B, "slot 5 -> BASIC");
assert_eq!(
out.row_status[3], B,
"slot 6 -> BASIC (tight by value, but BASIC by invariant)"
);
assert_eq!(out.row_status[4], B, "slot 7 -> BASIC");
}
#[test]
fn test_all_preserved_same_slots_same_order() {
let stored = make_stored_basis(3, 4, &[10, 11, 12, 13, 14], &[L, B, L, B, L], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 1.0, vec![0.5, 0.5]),
(11, 2.0, vec![0.5, 0.5]),
(12, 3.0, vec![0.5, 0.5]),
(13, 4.0, vec![0.5, 0.5]),
(14, 5.0, vec![0.5, 0.5]),
];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 5,
new_tight: 0,
new_slack: 0
}
);
assert_eq!(&out.row_status[3..8], &stored.basis.row_status[3..8]);
}
#[test]
fn test_drops_only() {
let stored = make_stored_basis(3, 4, &[10, 11, 12, 13, 14], &[L, B, L, B, L], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0, 0.0]),
(12, 0.0, vec![0.0, 0.0]),
(14, 0.0, vec![0.0, 0.0]),
];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 3,
new_tight: 0,
new_slack: 0
}
);
assert_eq!(out.row_status[3], stored.basis.row_status[3], "slot 10");
assert_eq!(out.row_status[4], stored.basis.row_status[5], "slot 12");
assert_eq!(out.row_status[5], stored.basis.row_status[7], "slot 14");
}
#[test]
fn test_reorder() {
let stored = make_stored_basis(3, 4, &[10, 11, 12, 13, 14], &[L, B, L, B, L], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(11, 0.0, vec![0.0, 0.0]),
(13, 0.0, vec![0.0, 0.0]),
(10, 0.0, vec![0.0, 0.0]),
(12, 0.0, vec![0.0, 0.0]),
(14, 0.0, vec![0.0, 0.0]),
];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 5,
new_tight: 0,
new_slack: 0
}
);
assert_eq!(
out.row_status[3], stored.basis.row_status[4],
"slot 11 -> stored pos 1"
);
assert_eq!(
out.row_status[4], stored.basis.row_status[6],
"slot 13 -> stored pos 3"
);
assert_eq!(
out.row_status[5], stored.basis.row_status[3],
"slot 10 -> stored pos 0"
);
assert_eq!(
out.row_status[6], stored.basis.row_status[5],
"slot 12 -> stored pos 2"
);
assert_eq!(
out.row_status[7], stored.basis.row_status[7],
"slot 14 -> stored pos 4"
);
}
#[test]
fn test_adds_only() {
let stored = make_stored_basis(3, 4, &[10, 11], &[L, B], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0, 0.0]),
(11, 0.0, vec![0.0, 0.0]),
(12, 9.0, vec![0.5, 0.5]),
(13, 0.0, vec![0.0, 0.0]),
];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 10.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 2,
new_tight: 0,
new_slack: 2
}
);
assert_eq!(&out.row_status[3..5], &stored.basis.row_status[3..5]);
assert_eq!(
out.row_status[5], B,
"slot 12 -> BASIC (no metadata, active_window=0)"
);
assert_eq!(out.row_status[6], B, "slot 13 -> BASIC");
}
#[test]
fn test_mixed_drop_and_add() {
let stored = make_stored_basis(3, 4, &[10, 11], &[L, B], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> =
vec![(11, 0.0, vec![0.0, 0.0]), (12, 0.0, vec![0.0, 0.0])];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 10.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 1,
new_tight: 0,
new_slack: 1
}
);
assert_eq!(
out.row_status[3], stored.basis.row_status[4],
"slot 11 preserved"
);
assert_eq!(out.row_status[4], B, "slot 12 new slack");
}
#[test]
fn test_empty_iterator_preserves_template_rows() {
let stored = make_stored_basis(3, 4, &[10, 11, 12], &[L, B, L], &[1.0, 2.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats, ReconstructionStats::default());
assert_eq!(out.row_status.len(), 3, "only template rows");
}
#[cfg(not(debug_assertions))]
#[test]
fn test_slot_lookup_growth_safe_in_release() {
let stored = make_stored_basis(3, 3, &[999], &[L], &[]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![(500, 0.0, vec![])];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 5];
let stats = reconstruct_basis(
&stored,
source_no_metadata(3, 3),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert!(lookup.len() >= 1000, "lookup must have grown");
assert_eq!(
stats,
ReconstructionStats {
preserved: 0,
new_tight: 0,
new_slack: 1
}
);
}
#[test]
fn test_forward_reconstruct_preserves_slots_after_churn() {
let mut pool = CutPool::new(16, 1, 1, 0);
pool.add_cut(0, 0, 1.0, &[1.0]);
pool.add_cut(1, 0, 2.0, &[2.0]);
pool.add_cut(2, 0, 3.0, &[3.0]);
let stored = make_stored_basis(2, 3, &[0, 1, 2], &[L, B, L], &[5.0]);
pool.deactivate(&[1]);
let active_after_churn: Vec<(usize, f64, Vec<f64>)> = pool
.active_cuts()
.map(|(s, i, c)| (s, i, c.to_vec()))
.collect();
assert_eq!(
active_after_churn.len(),
2,
"two cuts remain active after deactivating slot 1"
);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let stats = reconstruct_basis(
&stored,
source_no_metadata(2, 3),
active_after_churn
.iter()
.map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[5.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 2,
new_tight: 0,
new_slack: 0,
},
"churn must preserve surviving slots without new-cut churn",
);
assert_eq!(out.row_status[2], stored.basis.row_status[2], "slot 0");
assert_eq!(out.row_status[3], stored.basis.row_status[4], "slot 2");
}
#[test]
fn test_forward_reconstruct_three_new_slack_cuts() {
let stored = make_stored_basis(2, 3, &[0, 1], &[L, B], &[1.0]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(0, 1.0, vec![1.0]), (1, 2.0, vec![2.0]), (20, 0.0, vec![0.0]), (21, 1.0, vec![1.0]), (22, 2.0, vec![0.5]), ];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 32];
let stats = reconstruct_basis(
&stored,
source_no_metadata(2, 3),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 2,
new_tight: 0,
new_slack: 3,
},
"two preserved + three slack new",
);
assert_eq!(out.row_status[4], B, "slot 20 new slack");
assert_eq!(out.row_status[5], B, "slot 21 new slack");
assert_eq!(out.row_status[6], B, "slot 22 new slack");
}
#[test]
fn test_capture_metadata_invariants() {
let mut pool = CutPool::new(16, 2, 1, 0);
pool.add_cut(0, 0, 1.0, &[1.0, 2.0]);
pool.add_cut(1, 0, 2.0, &[1.5, 2.5]);
pool.add_cut(2, 0, 3.0, &[2.0, 3.0]);
let n_state = 2;
let base_row_count = 4;
let cut_row_count = 3;
let mut captured = CapturedBasis::new(
5, base_row_count + cut_row_count,
base_row_count,
cut_row_count,
n_state,
);
captured.cut_row_slots.clear();
#[allow(clippy::cast_possible_truncation)]
for (slot, _intercept, _coeffs) in pool.active_cuts().take(cut_row_count) {
captured.cut_row_slots.push(slot as u32);
}
captured.state_at_capture.clear();
let current_state = vec![10.0, 20.0];
captured.state_at_capture.extend_from_slice(¤t_state);
captured.base_row_count = base_row_count;
assert_eq!(
captured.cut_row_slots.len(),
pool.active_cuts().count(),
"cut_row_slots.len() must equal pool.active_cuts().count()",
);
assert_eq!(
captured.state_at_capture.len(),
n_state,
"state_at_capture.len() must equal n_state",
);
assert_eq!(
captured.base_row_count, base_row_count,
"base_row_count must equal templates[t].num_rows",
);
assert_eq!(
captured.basis.row_status.len(),
captured.base_row_count + captured.cut_row_slots.len(),
"metadata invariant: row_status.len() == base_row_count + cut_row_slots.len()",
);
}
fn make_baked_consistent_stored() -> CapturedBasis {
let mut s = make_stored_basis(10, 6, &[100, 101, 102], &[B, L, B], &[1.0, 2.0, 3.0]);
s.basis.col_status.clear();
s.basis.col_status.extend_from_slice(&[B, L, L, L, L, L]);
s
}
#[test]
fn reconstructed_basis_preserves_basic_count_invariant_backward_all_preserved() {
let stored = make_baked_consistent_stored();
let baked_base_rows = 10usize;
let num_cols = 6usize;
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(100, 0.0, vec![0.0, 0.0, 0.0]),
(101, 0.0, vec![0.0, 0.0, 0.0]),
(102, 0.0, vec![0.0, 0.0, 0.0]),
];
let delta_count = delta_cuts.len();
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 200];
let stats = reconstruct_basis(
&stored,
source_no_metadata(baked_base_rows, num_cols),
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0, 3.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 3, "all delta cuts preserved");
assert_eq!(
stats.new_tight, 0,
"no activity metadata — all new cuts fall back to BASIC"
);
assert_eq!(stats.new_slack, 0, "no new cuts");
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
baked_base_rows + delta_count,
"basic_count invariant: col_basic({col_basic}) + row_basic({row_basic}) \
== base_rows({baked_base_rows}) + delta({delta_count})",
);
}
#[test]
fn reconstructed_basis_preserves_basic_count_invariant_backward_with_new_cuts() {
let stored = make_baked_consistent_stored();
let baked_base_rows = 10usize;
let num_cols = 6usize;
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(100, 0.0, vec![0.0, 0.0, 0.0]),
(101, 0.0, vec![0.0, 0.0, 0.0]),
(102, 0.0, vec![0.0, 0.0, 0.0]),
(200, 5.0, vec![1.0, 0.0, 0.0]), (201, 0.0, vec![0.0, 0.0, 0.0]), ];
let delta_count = delta_cuts.len();
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 300];
let stats = reconstruct_basis(
&stored,
source_no_metadata(baked_base_rows, num_cols),
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0, 2.0, 3.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 3, "three preserved delta cuts");
assert_eq!(
stats.new_tight, 0,
"no activity metadata — new cuts fall back to BASIC"
);
assert_eq!(
stats.new_slack, 2,
"two new cuts classified BASIC (no metadata)"
);
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
baked_base_rows + delta_count,
"basic_count invariant: col_basic({col_basic}) + row_basic({row_basic}) \
== base_rows({baked_base_rows}) + delta({delta_count})",
);
assert_eq!(
out.row_status[baked_base_rows + 3],
B,
"slot 200 must be BASIC"
);
assert_eq!(
out.row_status[baked_base_rows + 4],
B,
"slot 201 must be BASIC"
);
}
#[test]
fn reconstructed_basis_preserves_basic_count_invariant_forward_all_preserved() {
let num_cols = 4usize;
let base_rows = 3usize;
let mut stored = make_stored_basis(base_rows, num_cols, &[10, 12, 14], &[B, B, B], &[1.0]);
stored.basis.col_status.clear();
stored.basis.col_status.extend_from_slice(&[B, L, L, L]);
stored.basis.row_status[0] = B;
stored.basis.row_status[1] = B;
stored.basis.row_status[2] = L;
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(12, 0.0, vec![0.0]),
(14, 0.0, vec![0.0]),
];
let num_cut = delta_cuts.len();
let num_row = base_rows + num_cut;
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(base_rows, num_cols),
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 3, "all 3 cuts preserved");
assert_eq!(stats.new_tight, 0);
assert_eq!(stats.new_slack, 0);
let demotions = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(
demotions, 0,
"no excess when all preserved and invariant holds"
);
let row_basic_after = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_after,
num_row,
"invariant holds: col_basic({col_basic}) + row_basic({row_basic_after}) \
== num_row({num_row})",
);
}
#[test]
fn reconstructed_basis_preserves_basic_count_invariant_forward_drops_with_lower() {
let base_rows = 1usize;
let num_cols = 1usize;
let mut stored = make_stored_basis(
base_rows,
num_cols,
&[10, 11, 12, 13],
&[B, B, L, B],
&[1.0],
);
stored.basis.col_status.clear();
stored.basis.col_status.extend_from_slice(&[B]);
stored.basis.row_status[0] = B;
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(col_basic, 1);
let stored_row_basic = stored.basis.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + stored_row_basic,
1 + 4,
"stored LP invariant sanity"
);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> =
vec![(10, 0.0, vec![0.0]), (11, 0.0, vec![0.0])];
let num_cut = delta_cuts.len();
let num_row = base_rows + num_cut;
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 20];
let stats = reconstruct_basis(
&stored,
source_no_metadata(base_rows, num_cols),
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 2, "both surviving cuts preserved");
assert_eq!(stats.new_tight, 0);
assert_eq!(stats.new_slack, 0);
let row_basic_before = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_before,
4,
"pre-enforce: total basic = 4"
);
let demotions = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demotions, 1, "demotions == dropped_lower == 1");
let row_basic_after = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_after,
num_row,
"invariant restored: col_basic({col_basic}) + row_basic({row_basic_after}) \
== num_row({num_row})",
);
}
#[test]
fn reconstructed_basis_preserves_invariant_on_baked_truncation() {
let num_cols = 3usize;
let n_state = 1usize;
let l_old = 5usize;
let l_new = 3usize;
let mut stored = make_stored_basis(l_old, num_cols, &[], &[], &[1.0]);
stored.basis.col_status.clear();
stored.basis.col_status.extend_from_slice(&[B, B, L]);
stored.basis.row_status.clear();
stored.basis.row_status.extend_from_slice(&[L, B, B, B, L]);
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
let stored_row_basic = stored.basis.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(col_basic + stored_row_basic, l_old, "stored LP invariant");
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let stats = reconstruct_basis(
&stored,
source_no_metadata(l_new, num_cols),
std::iter::empty::<(usize, f64, &[f64])>(),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats, ReconstructionStats::default());
assert_eq!(out.row_status.len(), l_new);
let row_basic_before = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_before,
l_new + 1,
"truncation leaves excess = 1 (one LOWER dropped from the tail)",
);
let mut out_noop = out.clone();
let noop_demotions = enforce_basic_count_invariant(&mut out_noop, l_new, l_new);
assert_eq!(
noop_demotions, 0,
"old caller args (base_row_count == num_row) cannot demote",
);
let noop_total = col_basic + out_noop.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
noop_total,
l_new + 1,
"old caller args leave the excess in place — this is the bug",
);
let demotions = enforce_basic_count_invariant(&mut out, l_new, n_state);
assert_eq!(demotions, 1, "fixed caller args demote one trailing BASIC");
let row_basic_after = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_after,
l_new,
"invariant restored after the fix",
);
}
#[test]
fn reconstructed_basis_preserves_basic_count_invariant_forward_new_cuts_after_drops() {
let base_rows = 1usize;
let num_cols = 1usize;
let mut stored = make_stored_basis(
base_rows,
num_cols,
&[10, 11, 12, 13],
&[B, B, L, B],
&[1.0],
);
stored.basis.col_status.clear();
stored.basis.col_status.extend_from_slice(&[B]);
stored.basis.row_status[0] = B;
let col_basic = stored.basis.col_status.iter().filter(|&&s| s == B).count();
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(11, 0.0, vec![0.0]),
(20, 0.0, vec![0.0]), (21, 0.0, vec![0.0]), ];
let num_cut = delta_cuts.len();
let num_row = base_rows + num_cut;
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 32];
let stats = reconstruct_basis(
&stored,
source_no_metadata(base_rows, num_cols),
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 2, "slots 10,11 preserved");
assert_eq!(stats.new_tight, 0);
assert_eq!(stats.new_slack, 2, "slots 20,21 new (BASIC)");
let row_basic_before = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_before,
6,
"pre-enforce total basic = 6"
);
let demotions = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(
demotions, 1,
"demotions == dropped_lower == 1 (new cuts don't add excess)"
);
let row_basic_after = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic_after,
num_row,
"invariant restored: col_basic({col_basic}) + row_basic({row_basic_after}) \
== num_row({num_row})",
);
}
#[test]
fn classifier_returns_basic_when_active_window_is_zero() {
let base_rows = 1usize;
let num_cols = 1usize;
let stored = make_stored_basis(base_rows, num_cols, &[], &[], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![(99, 0.0, vec![0.0])];
let meta = vec![meta_with_window(0)];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 128];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.new_tight, 0, "window=0 → BASIC (new_slack)");
assert_eq!(stats.new_slack, 1);
}
#[test]
fn classifier_returns_lower_when_active_window_has_recent_bit() {
let base_rows = 1usize;
let num_cols = 1usize;
let stored = make_stored_basis(base_rows, num_cols, &[5], &[L], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(5, 0.0, vec![0.0]), (0, 0.0, vec![0.0]), ];
let mut meta = vec![meta_with_window(0); 6];
meta[0] = meta_with_window(0b0000_0001);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 128];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 1, "slot 5 preserved");
assert_eq!(stats.new_tight, 1, "window bit 0 set → LOWER (new_tight)");
assert_eq!(stats.new_slack, 0);
assert_eq!(
out.row_status.last().copied(),
Some(L),
"new cut slot 0 must have LOWER status",
);
}
#[test]
fn classifier_ignores_active_window_outside_recent_window() {
let base_rows = 1usize;
let num_cols = 1usize;
let stored = make_stored_basis(base_rows, num_cols, &[], &[], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![(0, 0.0, vec![0.0])];
let meta = vec![meta_with_window(1u32 << DEFAULT_BASIS_ACTIVITY_WINDOW)];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 128];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.new_tight, 0, "bits outside window → BASIC");
assert_eq!(stats.new_slack, 1);
}
#[test]
fn scheme_1_symmetric_promotion_preserves_total_basic() {
let base_rows = 1usize;
let num_cols = 4usize;
let stored = make_stored_basis(
base_rows,
num_cols,
&[10, 11, 12, 13],
&[L, L, L, L],
&[1.0],
);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(11, 0.0, vec![0.0]),
(12, 0.0, vec![0.0]),
(13, 0.0, vec![0.0]),
(20, 0.0, vec![0.0]), (21, 0.0, vec![0.0]), ];
let num_row = base_rows + delta_cuts.len();
let mut meta = vec![meta_with_window(0); 22];
meta[10] = meta_with_window(0b0000_0001); meta[11] = meta_with_window(0b0000_0111); meta[12] = meta_with_window(0b0001_1111); meta[13] = meta_with_window(0b0111_1111); meta[20] = meta_with_window(0b0000_0001); meta[21] = meta_with_window(0b0000_0001);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 32];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 4, "4 slots preserved");
assert_eq!(stats.new_tight, 2, "2 new LOWER cuts");
assert_eq!(stats.new_slack, 0);
assert_eq!(out.row_status[0], B, "template row unchanged");
assert_eq!(
out.row_status[1], B,
"slot 10 (popcount=1) promoted LOWER → BASIC"
);
assert_eq!(
out.row_status[2], B,
"slot 11 (popcount=3) promoted LOWER → BASIC"
);
assert_eq!(out.row_status[3], L, "slot 12 (popcount=5) stays LOWER");
assert_eq!(out.row_status[4], L, "slot 13 (popcount=7) stays LOWER");
assert_eq!(out.row_status[5], L, "new slot 20 classified LOWER");
assert_eq!(out.row_status[6], L, "new slot 21 classified LOWER");
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demoted, 0, "Scheme 1 promotion makes enforcer a no-op");
}
#[test]
fn promotion_sort_ignores_bits_outside_recent_window() {
let base_rows = 1usize;
let num_cols = 2usize;
let stored = make_stored_basis(base_rows, num_cols, &[10, 11], &[L, L], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(11, 0.0, vec![0.0]),
(20, 0.0, vec![0.0]),
];
let num_row = base_rows + delta_cuts.len();
let mut meta = vec![meta_with_window(0); 22];
meta[10] = meta_with_window(0b1111_1111_1111_0000_0000_0000_0000_0000);
meta[11] = meta_with_window(0b0000_0000_0000_0000_0000_0000_0000_0010);
meta[20] = meta_with_window(0b0000_0000_0000_0000_0000_0000_0000_0001);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 32];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 2);
assert_eq!(stats.new_tight, 1);
assert_eq!(stats.new_slack, 0);
assert_eq!(out.row_status[0], B, "template unchanged");
assert_eq!(
out.row_status[1], B,
"slot 10 (masked popcount 0) promoted to BASIC ahead of slot 11 \
(masked popcount 1): masked sort selects by window bits, not full popcount"
);
assert_eq!(
out.row_status[2], L,
"slot 11 preserved LOWER under masked sort"
);
assert_eq!(out.row_status[3], L, "new cut at slot 20 classified LOWER");
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant must hold after G2 fix"
);
}
#[test]
fn scheme_2_fallback_when_deficit_exceeds_candidates() {
let base_rows = 1usize;
let num_cols = 2usize;
let stored = make_stored_basis(base_rows, num_cols, &[5, 6], &[L, L], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(5, 0.0, vec![0.0]), (6, 0.0, vec![0.0]), (20, 0.0, vec![0.0]), (21, 0.0, vec![0.0]), (22, 0.0, vec![0.0]), ];
let num_row = base_rows + delta_cuts.len();
let mut meta = vec![meta_with_window(0); 23];
meta[5] = meta_with_window(0b0000_0001); meta[6] = meta_with_window(0b0000_0011); meta[20] = meta_with_window(0b0000_0001); meta[21] = meta_with_window(0b0000_0001); meta[22] = meta_with_window(0b0000_0001);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 32];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 2, "2 slots preserved");
assert_eq!(
stats.new_tight, 2,
"3 LOWER guesses − 1 Scheme 2 override = 2 net new_tight"
);
assert_eq!(
stats.new_slack, 1,
"1 LOWER overridden to BASIC by Scheme 2 tail"
);
assert_eq!(out.row_status[0], B, "template row unchanged");
assert_eq!(
out.row_status[1], B,
"slot 5 promoted LOWER → BASIC by Scheme 1"
);
assert_eq!(
out.row_status[2], B,
"slot 6 promoted LOWER → BASIC by Scheme 1"
);
assert_eq!(out.row_status[3], L, "slot 20 classified LOWER (tight)");
assert_eq!(out.row_status[4], L, "slot 21 classified LOWER (tight)");
assert_eq!(
out.row_status[5], B,
"slot 22 overridden to BASIC by Scheme 2"
);
assert_eq!(out.row_status.len(), num_row);
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demoted, 0, "Scheme 1+2 together make enforcer a no-op");
}
#[test]
fn scheme_1_promotion_with_no_preserved_lowers_fallback_to_scheme_2() {
let base_rows = 1usize;
let num_cols = 0usize;
let stored = make_stored_basis(base_rows, num_cols, &[3], &[B], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(3, 0.0, vec![]), (10, 0.0, vec![]), ];
let num_row = base_rows + delta_cuts.len();
let mut meta = vec![meta_with_window(0); 11];
meta[10] = meta_with_window(0b0000_0001);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 1, "slot 3 preserved");
assert_eq!(
stats.new_tight, 0,
"Scheme 2 adjusted new_tight to 0 (1 LOWER overridden)"
);
assert_eq!(
stats.new_slack, 1,
"Scheme 2 override counts as new_slack += 1"
);
assert_eq!(out.row_status[0], B, "template row unchanged");
assert_eq!(out.row_status[1], B, "slot 3 preserved BASIC unchanged");
assert_eq!(
out.row_status[2], B,
"slot 10 overridden to BASIC by Scheme 2 (no Scheme 1 candidates)"
);
assert_eq!(out.row_status.len(), num_row);
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demoted, 0, "Scheme 2 alone makes enforcer a no-op");
}
#[test]
fn baked_path_preserves_slot_identity_across_cut_deactivation() {
let base_rows = 1usize;
let num_cols = 1usize;
let state_dim = 1usize;
#[allow(clippy::cast_possible_truncation)]
let stored_slots: Vec<u32> = (0u32..10).collect();
let stored_statuses: Vec<i32> = (0..10).map(|i| if i % 2 == 0 { L } else { B }).collect();
let stored =
make_stored_basis(base_rows, num_cols, &stored_slots, &stored_statuses, &[1.0]);
let mut pool = CutPool::new(16, state_dim, 1, 0);
for i in 0u64..10 {
#[allow(clippy::cast_precision_loss)]
let intercept = i as f64;
pool.add_cut(i, 0, intercept, &[1.0]);
}
pool.deactivate(&[3, 7]);
assert_eq!(pool.active_count(), 8, "8 active cuts after deactivation");
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &[], basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let mut promotion_scratch = PromotionScratch::default();
let padding = PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
};
let stats = reconstruct_basis(
&stored,
source,
pool.active_cuts(),
padding,
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 8, "all 8 surviving slots preserved");
assert_eq!(stats.new_tight, 0, "no new tight cuts");
assert_eq!(stats.new_slack, 0, "no new slack cuts");
let num_cut_rows = 8usize;
assert_eq!(out.row_status.len(), base_rows + num_cut_rows);
let expected = [L, B, L, L, B, L, L, B];
for (i, &expected_status) in expected.iter().enumerate() {
assert_eq!(
out.row_status[base_rows + i],
expected_status,
"cut row {i}: expected {expected_status}",
);
}
}
#[test]
fn baked_path_classifier_fires_on_recent_activity() {
let base_rows = 1usize;
let num_cols = 3usize;
let state_dim = 1usize;
let stored = make_stored_basis(base_rows, num_cols, &[100, 101, 102], &[L, L, L], &[1.0]);
let mut pool = CutPool::new(128, state_dim, 1, 0);
for i in 0u64..5 {
pool.add_cut(i, 0, 0.0, &[0.0]);
}
for i in 100u64..103 {
pool.add_cut(i, 0, 0.0, &[0.0]);
}
for s in [0usize, 1, 3, 4, 100, 101, 102] {
pool.metadata[s].active_window = 0;
}
assert_eq!(pool.active_count(), 8, "5 new + 3 preserved cuts active");
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &pool.metadata,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 128];
let mut promotion_scratch = PromotionScratch::default();
let padding = PaddingContext {
state: &[1.0],
theta: 100.0,
tolerance: 1e-7,
};
let stats = reconstruct_basis(
&stored,
source,
pool.active_cuts(),
padding,
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 3, "slots 100,101,102 preserved");
assert_eq!(stats.new_tight, 1, "slot 2 classified LOWER by classifier");
assert_eq!(stats.new_slack, 4, "slots 0,1,3,4 classified BASIC");
assert_eq!(out.row_status[base_rows], B, "slot 0 → BASIC");
assert_eq!(out.row_status[base_rows + 1], B, "slot 1 → BASIC");
assert_eq!(
out.row_status[base_rows + 2],
L,
"slot 2 → LOWER (classifier)"
);
assert_eq!(out.row_status[base_rows + 3], B, "slot 3 → BASIC");
assert_eq!(out.row_status[base_rows + 4], B, "slot 4 → BASIC");
assert_eq!(
out.row_status[base_rows + 5],
B,
"slot 100 → BASIC (Scheme 1 promoted from LOWER)"
);
assert_eq!(
out.row_status[base_rows + 6],
L,
"slot 101 → LOWER (preserved, not promoted)"
);
assert_eq!(
out.row_status[base_rows + 7],
L,
"slot 102 → LOWER (preserved, not promoted)"
);
let num_row = base_rows + 8; let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(
demoted, 0,
"Scheme 1 maintained the invariant; enforcer is no-op"
);
}
#[test]
fn classifier_returns_lower_for_freshly_generated_cut_same_iteration() {
let base_rows = 0usize;
let num_cols = 0usize;
let stored = CapturedBasis::new(num_cols, base_rows, base_rows, 0, 0);
let mut pool = CutPool::new(8, 3, 3, 0);
pool.add_cut(
1,
0,
10.0,
&[1.0, 2.0, 3.0],
);
assert_eq!(
pool.metadata[3].active_window,
crate::basis_reconstruct::SEED_BIT,
"G1 seed: add_cut must initialise active_window to SEED_BIT at slot 3"
);
let num_row = base_rows + 1; let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 8];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: pool.metadata.as_slice(),
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
pool.active_cuts(),
PaddingContext {
state: &[0.0, 0.0, 0.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 0, "no stored cuts — nothing preserved");
assert_eq!(
stats.new_tight, 0,
"Scheme 2 adjusted new_tight to 0 (G1-LOWER overridden)"
);
assert_eq!(
stats.new_slack, 1,
"Scheme 2 override counts as new_slack (no Scheme 1 candidates)"
);
assert_eq!(
out.row_status.len(),
num_row,
"one cut row total (no template rows)"
);
assert_eq!(
out.row_status[0], B,
"slot 3: G1-LOWER overridden to BASIC by Scheme 2"
);
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demoted, 0, "Scheme 2 alone makes enforcer a no-op");
}
#[test]
fn classifier_returns_lower_for_freshly_generated_cut_with_scheme_1_absorbed_demotion() {
let base_rows = 1usize;
let num_cols = 1usize;
let mut pool = CutPool::new(8, 3, 3, 0);
pool.add_cut(
0,
1,
1.0,
&[0.0, 0.0, 0.0],
);
pool.metadata[1].active_window = 0;
pool.add_cut(
1,
0,
5.0,
&[1.0, 2.0, 3.0],
);
assert_eq!(
pool.metadata[3].active_window,
crate::basis_reconstruct::SEED_BIT,
"G1 seed: slot 3 must have active_window=SEED_BIT after add_cut"
);
let stored = make_stored_basis(base_rows, num_cols, &[1], &[L], &[0.0, 0.0, 0.0]);
let num_row = base_rows + 2; let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 8];
let mut promotion_scratch = PromotionScratch::default();
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: pool.metadata.as_slice(),
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let stats = reconstruct_basis(
&stored,
source,
pool.active_cuts(),
PaddingContext {
state: &[0.0, 0.0, 0.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut promotion_scratch,
);
assert_eq!(stats.preserved, 1, "slot 1 preserved from stored basis");
assert_eq!(
stats.new_tight, 1,
"slot 3: G1 seed (active_window=SEED_BIT) → LOWER (new_tight=1); Scheme 1 absorbed the deficit"
);
assert_eq!(stats.new_slack, 0, "no BASIC-classified new cuts");
assert_eq!(out.row_status.len(), num_row);
assert_eq!(out.row_status[0], B, "template row unchanged");
assert_eq!(
out.row_status[1], B,
"slot 1: preserved-LOWER promoted to BASIC by Scheme 1 (T3a direction)"
);
assert_eq!(
out.row_status[2], L,
"slot 3: G1-seeded LOWER survives — Scheme 1 absorbed the deficit via slot 1 promotion"
);
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic + row_basic == num_row"
);
let demoted = enforce_basic_count_invariant(&mut out, num_row, base_rows);
assert_eq!(demoted, 0, "Scheme 1 promotion makes enforcer a no-op");
}
fn meta_for_tie_break(lai_10: u64, lai_20: u64, lai_30: u64) -> Vec<CutMetadata> {
let mut meta = vec![meta_with_window(0); 41];
meta[10] = CutMetadata {
active_window: 0b0000_0001,
last_active_iter: lai_10,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
meta[20] = CutMetadata {
active_window: 0b0000_0001,
last_active_iter: lai_20,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
meta[30] = CutMetadata {
active_window: 0b0000_0001,
last_active_iter: lai_30,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
meta[40] = CutMetadata {
active_window: SEED_BIT,
last_active_iter: 0,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
meta
}
#[test]
fn promotion_sort_breaks_popcount_ties_by_last_active_iter() {
let base_rows = 1usize;
let num_cols = 3usize;
let stored = make_stored_basis(base_rows, num_cols, &[10, 20, 30], &[L, L, L], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(20, 0.0, vec![0.0]),
(30, 0.0, vec![0.0]),
(40, 0.0, vec![0.0]), ];
let num_row = base_rows + delta_cuts.len();
let meta = meta_for_tie_break( 1, 5, 9);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 48];
let stats = reconstruct_basis(
&stored,
ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
},
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 3, "slots 10, 20, 30 preserved");
assert_eq!(stats.new_tight, 1, "slot 40 classified LOWER via SEED_BIT");
assert_eq!(stats.new_slack, 0);
assert_eq!(out.row_status[0], B, "template row");
assert_eq!(
out.row_status[1], B,
"T5a G3: slot 10 (last_active_iter=1) promoted — most-stale wins tie"
);
assert_eq!(out.row_status[2], L, "slot 20 stays LOWER");
assert_eq!(out.row_status[3], L, "slot 30 stays LOWER");
assert_eq!(out.row_status[4], L, "slot 40 new LOWER");
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(col_basic + row_basic, num_row, "HiGHS invariant");
}
#[test]
fn promotion_sort_breaks_popcount_ties_by_last_active_iter_reversed() {
let base_rows = 1usize;
let num_cols = 3usize;
let stored = make_stored_basis(base_rows, num_cols, &[10, 20, 30], &[L, L, L], &[1.0]);
let delta_cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(10, 0.0, vec![0.0]),
(20, 0.0, vec![0.0]),
(30, 0.0, vec![0.0]),
(40, 0.0, vec![0.0]),
];
let num_row = base_rows + delta_cuts.len();
let meta = meta_for_tie_break( 9, 5, 1);
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 48];
let stats = reconstruct_basis(
&stored,
ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
},
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(stats.preserved, 3);
assert_eq!(stats.new_tight, 1);
assert_eq!(out.row_status[1], L, "slot 10 stays LOWER (not most-stale)");
assert_eq!(out.row_status[2], L, "slot 20 stays LOWER");
assert_eq!(
out.row_status[3], B,
"T5a G3: slot 30 (last_active_iter=1) promoted when it is most-stale"
);
assert_eq!(out.row_status[4], L, "slot 40 new LOWER");
let row_basic = out.row_status.iter().filter(|&&s| s == B).count();
let col_basic = out.col_status.iter().filter(|&&s| s == B).count();
assert_eq!(col_basic + row_basic, num_row, "HiGHS invariant (reversed)");
}
#[test]
#[allow(clippy::too_many_lines)]
fn promotion_select_nth_produces_deterministic_promotion_set_at_n_200() {
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
let base_rows = 1usize;
let num_cols = 200usize;
let preserved_slots: Vec<u32> = (0u32..200).collect();
let stored =
make_stored_basis(base_rows, num_cols, &preserved_slots, &vec![L; 200], &[1.0]);
let mut delta_cuts: Vec<(usize, f64, Vec<f64>)> = preserved_slots
.iter()
.map(|&s| (s as usize, 0.0f64, vec![0.0f64]))
.collect();
for s in 200usize..250 {
delta_cuts.push((s, 0.0, vec![0.0]));
}
let mut rng = StdRng::seed_from_u64(0xCAFE_BABE_DEAD_BEEF);
let mut meta = vec![meta_with_window(0); 260];
for (s, slot_meta) in meta.iter_mut().enumerate().take(200) {
#[allow(clippy::cast_possible_truncation)]
let pc = (s % 6) as u32;
let aw = if pc == 0 { 0u32 } else { (1u32 << pc) - 1 };
let lai: u64 = rng.random_range(0u64..100);
*slot_meta = CutMetadata {
active_window: aw,
last_active_iter: lai,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
}
for slot_meta in meta.iter_mut().take(250).skip(200) {
*slot_meta = CutMetadata {
active_window: crate::basis_reconstruct::SEED_BIT,
last_active_iter: 0,
iteration_generated: 0,
forward_pass_index: 0,
active_count: 0,
};
}
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
let mut out1 = Basis::new(0, 0);
let mut lookup1: Vec<Option<u32>> = vec![None; 260];
let mut scratch1 = PromotionScratch::default();
let stats1 = reconstruct_basis(
&stored,
source,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out1,
&mut lookup1,
&mut scratch1,
);
assert_eq!(stats1.preserved, 200, "200 preserved");
assert_eq!(stats1.new_tight, 50, "50 new LOWER (scheme1 absorbed all)");
assert_eq!(stats1.new_slack, 0);
let promoted_set_1: std::collections::BTreeSet<usize> = (0..200)
.filter(|&i| out1.row_status[base_rows + i] == B)
.collect();
assert_eq!(promoted_set_1.len(), 50, "exactly 50 promoted in run 1");
let stored2 =
make_stored_basis(base_rows, num_cols, &preserved_slots, &vec![L; 200], &[1.0]);
let mut out2 = Basis::new(0, 0);
let mut lookup2: Vec<Option<u32>> = vec![None; 260];
let mut scratch2 = PromotionScratch::default();
let source2 = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: base_rows,
num_cols,
},
cut_metadata: &meta,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW,
};
reconstruct_basis(
&stored2,
source2,
delta_cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0],
theta: 0.0,
tolerance: 1e-7,
},
&mut out2,
&mut lookup2,
&mut scratch2,
);
let promoted_set_2: std::collections::BTreeSet<usize> = (0..200)
.filter(|&i| out2.row_status[base_rows + i] == B)
.collect();
assert_eq!(promoted_set_2.len(), 50, "exactly 50 promoted in run 2");
assert_eq!(
promoted_set_1, promoted_set_2,
"T5a AC6: select_nth_unstable_by_key must produce identical promotion \
sets across two calls on identical input data"
);
let num_row = base_rows + 250; let col_basic = out1.col_status.iter().filter(|&&s| s == B).count();
let row_basic = out1.row_status.iter().filter(|&&s| s == B).count();
assert_eq!(
col_basic + row_basic,
num_row,
"HiGHS invariant: col_basic({col_basic}) + row_basic({row_basic}) \
== num_row({num_row})"
);
}
#[test]
fn reconstruct_basis_honors_runtime_basis_activity_window() {
let stored = make_stored_basis(
1, 2, &[0], &[L], &[1.0_f64], );
let outside_default_bit: u32 = 1u32 << 5;
let metadata = [
meta_with_window(0), meta_with_window(outside_default_bit), ];
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(0, 0.0, vec![0.0]), (1, 0.0, vec![0.0]), ];
{
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: 1,
num_cols: 2,
},
cut_metadata: &metadata,
basis_activity_window: DEFAULT_BASIS_ACTIVITY_WINDOW, };
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 8];
let stats = reconstruct_basis(
&stored,
source,
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0_f64],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 1,
new_tight: 0,
new_slack: 1,
},
"window=5: new cut outside mask → BASIC, preserved stays LOWER"
);
assert_eq!(out.row_status[2], B, "window=5: new cut row must be BASIC");
}
{
let source = ReconstructionSource {
target: ReconstructionTarget {
base_row_count: 1,
num_cols: 2,
},
cut_metadata: &metadata,
basis_activity_window: 10,
};
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 8];
let stats = reconstruct_basis(
&stored,
source,
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[1.0_f64],
theta: 100.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut PromotionScratch::default(),
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 1,
new_tight: 1,
new_slack: 0,
},
"window=10: new cut inside mask → LOWER; Scheme 1 promotes slot-0 to BASIC"
);
assert_eq!(
out.row_status[1], B,
"window=10: slot-0 preserved-LOWER must be promoted to BASIC"
);
assert_eq!(
out.row_status[2], L,
"window=10: new cut row must remain LOWER after Scheme 1"
);
}
}
#[test]
fn reconstruct_basis_phase_helpers_produce_same_result() {
let stored = make_stored_basis(2, 4, &[3, 7], &[L, B], &[0.5, 1.5]);
let cuts: Vec<(usize, f64, Vec<f64>)> = vec![
(3, 2.0, vec![1.0, 0.0]), (9, 5.0, vec![0.0, 1.0]), ];
let mut out = Basis::new(0, 0);
let mut lookup: Vec<Option<u32>> = vec![None; 16];
let mut scratch = PromotionScratch::default();
let stats = reconstruct_basis(
&stored,
source_no_metadata(2, 4),
cuts.iter().map(|(s, i, c)| (*s, *i, c.as_slice())),
PaddingContext {
state: &[0.5, 1.5],
theta: 10.0,
tolerance: 1e-7,
},
&mut out,
&mut lookup,
&mut scratch,
);
assert_eq!(
out.col_status.len(),
4,
"col count must equal target num_cols"
);
assert!(
out.col_status.iter().all(|&s| s == B),
"all cols must be BASIC"
);
assert_eq!(
out.row_status.len(),
4,
"total row count = 2 template + 2 cut"
);
assert_eq!(out.row_status[0], B, "template row 0 must be BASIC");
assert_eq!(out.row_status[1], B, "template row 1 must be BASIC");
assert_eq!(out.row_status[2], L, "slot 3 (preserved) must be LOWER");
assert_eq!(
out.row_status[3], B,
"slot 9 (new, no activity) must be BASIC"
);
assert_eq!(
stats,
ReconstructionStats {
preserved: 1,
new_tight: 0,
new_slack: 1
},
);
}
}