#[derive(Debug, Clone)]
pub struct CutMetadata {
pub iteration_generated: u64,
pub forward_pass_index: u32,
pub active_count: u64,
pub last_active_iter: u64,
pub domination_count: u64,
}
#[derive(Debug, Clone)]
pub struct DeactivationSet {
pub stage_index: u32,
pub indices: Vec<u32>,
}
#[derive(Debug, Clone)]
pub enum CutSelectionStrategy {
Level1 {
threshold: u64,
check_frequency: u64,
},
Lml1 {
memory_window: u64,
check_frequency: u64,
},
Dominated {
threshold: f64,
check_frequency: u64,
},
}
impl CutSelectionStrategy {
#[must_use]
pub fn should_run(&self, iteration: u64) -> bool {
let freq = match self {
Self::Level1 {
check_frequency, ..
}
| Self::Lml1 {
check_frequency, ..
}
| Self::Dominated {
check_frequency, ..
} => *check_frequency,
};
iteration > 0 && iteration % freq == 0
}
#[must_use]
pub fn select(
&self,
pool: &crate::cut::CutPool,
visited_states: &[f64],
current_iteration: u64,
) -> DeactivationSet {
self.select_for_stage(pool, visited_states, current_iteration, 0)
}
#[must_use]
pub fn select_for_stage(
&self,
pool: &crate::cut::CutPool,
visited_states: &[f64],
current_iteration: u64,
stage_index: u32,
) -> DeactivationSet {
let populated = pool.populated_count;
let metadata = &pool.metadata[..populated];
let active = &pool.active[..populated];
#[allow(clippy::cast_possible_truncation)]
let indices = match self {
Self::Level1 { threshold, .. } => metadata
.iter()
.enumerate()
.filter(|(i, m)| {
active[*i]
&& m.active_count <= *threshold
&& m.iteration_generated < current_iteration
})
.map(|(i, _)| i as u32)
.collect(),
Self::Lml1 { memory_window, .. } => metadata
.iter()
.enumerate()
.filter(|(i, m)| {
active[*i]
&& m.iteration_generated < current_iteration
&& current_iteration.saturating_sub(m.last_active_iter) > *memory_window
})
.map(|(i, _)| i as u32)
.collect(),
Self::Dominated { threshold, .. } => {
select_dominated(pool, visited_states, *threshold, current_iteration)
}
};
DeactivationSet {
stage_index,
indices,
}
}
pub fn update_activity(
&self,
metadata: &mut CutMetadata,
is_binding: bool,
current_iteration: u64,
) {
if !is_binding {
return;
}
match self {
Self::Level1 { .. } => {
metadata.active_count += 1;
}
Self::Lml1 { .. } => {
metadata.last_active_iter = current_iteration;
}
Self::Dominated { .. } => {
metadata.domination_count = 0;
}
}
}
}
#[allow(clippy::cast_possible_truncation, clippy::needless_range_loop)]
fn select_dominated(
pool: &crate::cut::CutPool,
visited_states: &[f64],
threshold: f64,
current_iteration: u64,
) -> Vec<u32> {
let populated = pool.populated_count;
let n_state = pool.state_dimension;
if visited_states.is_empty() || n_state == 0 {
return vec![];
}
if pool.active_count() < 2 {
return vec![];
}
let mut is_candidate: Vec<bool> = pool.active[..populated]
.iter()
.zip(pool.metadata[..populated].iter())
.map(|(&a, m)| a && m.iteration_generated < current_iteration)
.collect();
let mut n_candidates: usize = is_candidate.iter().filter(|&&c| c).count();
if n_candidates == 0 {
return vec![];
}
let mut scratch = vec![0.0_f64; populated];
for x_hat in visited_states.chunks_exact(n_state) {
let mut max_val = f64::NEG_INFINITY;
for k in 0..populated {
if pool.active[k] {
let coeff_start = k * n_state;
let val = pool.intercepts[k]
+ pool.coefficients[coeff_start..coeff_start + n_state]
.iter()
.zip(x_hat)
.map(|(c, x)| c * x)
.sum::<f64>();
scratch[k] = val;
if val > max_val {
max_val = val;
}
}
}
let cutoff = max_val - threshold;
for k in 0..populated {
if is_candidate[k] && scratch[k] >= cutoff {
is_candidate[k] = false;
n_candidates -= 1;
}
}
if n_candidates == 0 {
break;
}
}
(0..populated)
.filter(|&k| is_candidate[k])
.map(|k| k as u32)
.collect()
}
pub fn parse_cut_selection_config(
config: &cobre_io::config::CutSelectionConfig,
) -> Result<Option<CutSelectionStrategy>, String> {
let enabled = config.enabled.unwrap_or(false);
if !enabled {
return Ok(None);
}
let method = config
.method
.as_deref()
.ok_or_else(|| "cut_selection.enabled is true but method is not specified".to_string())?;
let threshold = config.threshold.unwrap_or(0);
let check_frequency = config.check_frequency.unwrap_or(5);
if check_frequency == 0 {
return Err("cut_selection.check_frequency must be > 0".to_string());
}
match method {
"level1" => Ok(Some(CutSelectionStrategy::Level1 {
threshold: u64::from(threshold),
check_frequency: u64::from(check_frequency),
})),
"lml1" => {
let window = config.memory_window.unwrap_or(threshold);
Ok(Some(CutSelectionStrategy::Lml1 {
memory_window: u64::from(window),
check_frequency: u64::from(check_frequency),
}))
}
"domination" => {
let epsilon = config
.domination_epsilon
.unwrap_or_else(|| f64::from(threshold));
Ok(Some(CutSelectionStrategy::Dominated {
threshold: epsilon,
check_frequency: u64::from(check_frequency),
}))
}
other => Err(format!("unknown cut_selection.method: \"{other}\"")),
}
}
#[cfg(test)]
mod tests {
use super::parse_cut_selection_config;
use super::{CutMetadata, CutSelectionStrategy, DeactivationSet};
use crate::cut::CutPool;
use cobre_io::config::CutSelectionConfig;
fn make_meta(active_count: u64, last_active_iter: u64, domination_count: u64) -> CutMetadata {
CutMetadata {
iteration_generated: 1,
forward_pass_index: 0,
active_count,
last_active_iter,
domination_count,
}
}
#[allow(clippy::cast_possible_truncation)]
fn make_pool(metadata: &[CutMetadata], active: &[bool]) -> CutPool {
let n = metadata.len();
let mut pool = CutPool::new(n, 1, 1, 0);
for i in 0..n {
pool.add_cut(0, i as u32, 0.0, &[0.0]);
}
pool.metadata[..n].clone_from_slice(metadata);
pool.active[..n].clone_from_slice(active);
pool.cached_active_count = active.iter().filter(|&&a| a).count();
pool
}
#[test]
fn should_run_false_at_zero() {
let s = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
assert!(!s.should_run(0));
}
#[test]
fn should_run_false_between_multiples() {
let s = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
assert!(!s.should_run(3));
assert!(!s.should_run(7));
}
#[test]
fn should_run_true_at_multiples() {
let s = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
assert!(s.should_run(5));
assert!(s.should_run(10));
assert!(s.should_run(15));
}
#[test]
fn should_run_lml1_respects_check_frequency() {
let s = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
assert!(!s.should_run(0));
assert!(!s.should_run(3));
assert!(s.should_run(5));
assert!(s.should_run(10));
}
#[test]
fn should_run_dominated_respects_check_frequency() {
let s = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 10,
};
assert!(!s.should_run(5));
assert!(s.should_run(10));
}
#[test]
fn level1_deactivates_zero_activity_cuts() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(0, 1, 0), make_meta(1, 5, 0)], &[true, true]);
let deact = strategy.select(&pool, &[], 10);
assert_eq!(
deact.indices,
vec![0],
"only the inactive cut is deactivated"
);
}
#[test]
fn level1_retains_positive_activity_cuts() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(3, 1, 0), make_meta(7, 5, 0)], &[true, true]);
let deact = strategy.select(&pool, &[], 10);
assert!(
deact.indices.is_empty(),
"no cuts should be deactivated when all have activity"
);
}
#[test]
fn level1_threshold_1_deactivates_cuts_with_count_at_most_1() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 1,
check_frequency: 5,
};
let pool = make_pool(
&[make_meta(0, 1, 0), make_meta(1, 5, 0), make_meta(2, 8, 0)],
&[true, true, true],
);
let deact = strategy.select(&pool, &[], 10);
assert_eq!(deact.indices, vec![0, 1]);
}
#[test]
fn level1_empty_metadata_returns_empty_set() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = CutPool::new(0, 1, 1, 0);
let deact = strategy.select(&pool, &[], 10);
assert!(deact.indices.is_empty());
}
#[test]
fn lml1_deactivates_cuts_outside_memory_window() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(0, 5, 0)], &[true]);
let deact = strategy.select(&pool, &[], 20);
assert_eq!(deact.indices, vec![0]);
}
#[test]
fn lml1_retains_cuts_within_memory_window() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(0, 12, 0)], &[true]);
let deact = strategy.select(&pool, &[], 20);
assert!(deact.indices.is_empty());
}
#[test]
fn lml1_retains_cuts_exactly_at_boundary() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(0, 10, 0)], &[true]);
let deact = strategy.select(&pool, &[], 20);
assert!(
deact.indices.is_empty(),
"boundary case: exactly at window edge, retained"
);
}
#[test]
fn lml1_mixed_cuts_deactivates_correct_indices() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let pool = make_pool(
&[make_meta(0, 5, 0), make_meta(0, 12, 0), make_meta(0, 1, 0)],
&[true, true, true],
);
let deact = strategy.select(&pool, &[], 20);
assert_eq!(deact.indices, vec![0, 2]);
}
#[test]
fn dominated_select_always_returns_empty_set() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.001,
check_frequency: 10,
};
let pool = make_pool(&[make_meta(0, 1, 5), make_meta(0, 1, 10)], &[true, true]);
let deact = strategy.select(&pool, &[], 20);
assert!(deact.indices.is_empty());
}
#[test]
fn level1_update_activity_increments_active_count_when_binding() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let mut meta = make_meta(0, 1, 0);
strategy.update_activity(&mut meta, true, 5);
assert_eq!(meta.active_count, 1);
strategy.update_activity(&mut meta, true, 6);
assert_eq!(meta.active_count, 2);
}
#[test]
fn level1_update_activity_does_nothing_when_not_binding() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let mut meta = make_meta(3, 1, 0);
strategy.update_activity(&mut meta, false, 5);
assert_eq!(meta.active_count, 3, "must not modify when not binding");
}
#[test]
fn lml1_update_activity_sets_last_active_iter_when_binding() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let mut meta = make_meta(0, 1, 0);
strategy.update_activity(&mut meta, true, 15);
assert_eq!(meta.last_active_iter, 15);
}
#[test]
fn lml1_update_activity_does_nothing_when_not_binding() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let mut meta = make_meta(0, 7, 0);
strategy.update_activity(&mut meta, false, 15);
assert_eq!(meta.last_active_iter, 7, "must not modify when not binding");
}
#[test]
fn dominated_update_activity_resets_domination_count_when_binding() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.001,
check_frequency: 10,
};
let mut meta = make_meta(0, 1, 42);
strategy.update_activity(&mut meta, true, 10);
assert_eq!(
meta.domination_count, 0,
"domination_count must be reset when cut is binding"
);
}
#[test]
fn dominated_update_activity_does_nothing_when_not_binding() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.001,
check_frequency: 10,
};
let mut meta = make_meta(0, 1, 42);
strategy.update_activity(&mut meta, false, 10);
assert_eq!(
meta.domination_count, 42,
"must not modify when not binding"
);
}
#[test]
fn ac_level1_threshold_0_deactivates_zero_activity_cut() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = make_pool(
&[CutMetadata {
iteration_generated: 1,
forward_pass_index: 0,
active_count: 0,
last_active_iter: 1,
domination_count: 0,
}],
&[true],
);
let deact = strategy.select(&pool, &[], 10);
assert!(deact.indices.contains(&0));
}
#[test]
fn ac_lml1_deactivates_cut_outside_memory_window() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 10,
check_frequency: 5,
};
let pool = make_pool(
&[CutMetadata {
iteration_generated: 1,
forward_pass_index: 0,
active_count: 0,
last_active_iter: 5,
domination_count: 0,
}],
&[true],
);
let deact = strategy.select(&pool, &[], 20);
assert!(deact.indices.contains(&0));
}
#[test]
fn select_for_stage_sets_stage_index() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = make_pool(&[make_meta(0, 1, 0)], &[true]);
let deact = strategy.select_for_stage(&pool, &[], 10, 7);
assert_eq!(deact.stage_index, 7);
}
#[test]
fn select_sets_stage_index_to_zero() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
};
let pool = CutPool::new(0, 1, 1, 0);
let deact = strategy.select(&pool, &[], 10);
assert_eq!(deact.stage_index, 0);
}
#[test]
fn deactivation_set_derives_debug_and_clone() {
let deact = DeactivationSet {
stage_index: 2,
indices: vec![0, 3, 7],
};
let cloned = deact.clone();
assert_eq!(cloned.stage_index, 2);
assert_eq!(cloned.indices, vec![0, 3, 7]);
assert!(!format!("{deact:?}").is_empty());
}
#[test]
fn cut_metadata_derives_debug_and_clone() {
let meta = make_meta(5, 10, 2);
let cloned = meta.clone();
assert_eq!(cloned.active_count, 5);
assert!(!format!("{meta:?}").is_empty());
}
#[test]
fn test_parse_disabled_default() {
let cfg = CutSelectionConfig::default();
let result = parse_cut_selection_config(&cfg);
assert!(result.is_ok());
assert!(
result.unwrap().is_none(),
"default config must produce None (disabled)"
);
}
#[test]
fn test_parse_level1() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: Some("level1".to_string()),
threshold: Some(0),
check_frequency: Some(5),
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_ok());
let strategy = result
.unwrap()
.expect("must produce Some for enabled level1");
assert!(
matches!(
strategy,
CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 5,
}
),
"unexpected variant: {strategy:?}"
);
}
#[test]
fn test_parse_lml1() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: Some("lml1".to_string()),
threshold: None,
check_frequency: None,
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_ok());
let strategy = result.unwrap().expect("must produce Some for enabled lml1");
assert!(
matches!(
strategy,
CutSelectionStrategy::Lml1 {
memory_window: 0,
check_frequency: 5,
}
),
"unexpected variant: {strategy:?}"
);
}
#[test]
fn test_parse_domination() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: Some("domination".to_string()),
threshold: Some(0),
check_frequency: Some(10),
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_ok());
let strategy = result
.unwrap()
.expect("must produce Some for enabled domination");
assert!(
matches!(
strategy,
CutSelectionStrategy::Dominated {
threshold,
check_frequency: 10,
} if threshold == 0.0
),
"unexpected variant: {strategy:?}"
);
}
#[test]
fn test_parse_unknown_method() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: Some("bogus".to_string()),
threshold: None,
check_frequency: None,
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_err());
let msg = result.unwrap_err();
assert!(
msg.contains("bogus"),
"error message must contain the unrecognized method name, got: {msg}"
);
}
#[test]
fn test_parse_enabled_without_method() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: None,
threshold: None,
check_frequency: None,
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_err());
}
#[test]
fn test_parse_enabled_false_with_method_returns_none() {
let cfg = CutSelectionConfig {
enabled: Some(false),
method: Some("level1".to_string()),
threshold: None,
check_frequency: None,
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg).unwrap();
assert!(
result.is_none(),
"enabled=false must return None even when method is set"
);
}
#[test]
fn test_parse_zero_check_frequency() {
let cfg = CutSelectionConfig {
enabled: Some(true),
method: Some("level1".to_string()),
threshold: None,
check_frequency: Some(0),
cut_activity_tolerance: None,
memory_window: None,
domination_epsilon: None,
};
let result = parse_cut_selection_config(&cfg);
assert!(result.is_err());
let msg = result.unwrap_err();
assert!(
msg.contains("check_frequency"),
"error message must mention check_frequency, got: {msg}"
);
}
#[test]
fn select_skips_already_inactive_slots() {
let mut pool = CutPool::new(10, 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]); assert_eq!(pool.active_count(), 3);
pool.metadata[1].active_count = 5;
pool.metadata[2].active_count = 3;
pool.deactivate(&[0]);
assert_eq!(pool.active_count(), 2);
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 1,
};
let deact = strategy.select_for_stage(&pool, &[], 5, 0);
assert!(
deact.indices.is_empty(),
"no cuts should be selected: slot 0 is already inactive, \
slots 1 and 2 have activity"
);
}
#[test]
fn lml1_memory_window_boundary_behavior() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 3,
check_frequency: 1,
};
let pool = make_pool(
&[
make_meta(0, 1, 0), make_meta(0, 5, 0), make_meta(0, 7, 0), make_meta(0, 8, 0), make_meta(0, 10, 0), ],
&[true; 5],
);
let deact = strategy.select_for_stage(&pool, &[], 10, 0);
assert_eq!(
deact.indices,
vec![0, 1],
"only cuts with last_active_iter 1 and 5 should be deactivated"
);
}
#[test]
fn level1_spares_cuts_from_current_iteration() {
let strategy = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 1,
};
let pool = make_pool(
&[
CutMetadata {
iteration_generated: 10, forward_pass_index: 0,
active_count: 0,
last_active_iter: 10,
domination_count: 0,
},
CutMetadata {
iteration_generated: 5, forward_pass_index: 0,
active_count: 0,
last_active_iter: 5,
domination_count: 0,
},
],
&[true, true],
);
let deact = strategy.select(&pool, &[], 10);
assert_eq!(
deact.indices,
vec![1],
"only the older cut (iter 5) should be deactivated; \
the current-iteration cut (iter 10) must be spared"
);
}
#[test]
fn lml1_spares_cuts_from_current_iteration() {
let strategy = CutSelectionStrategy::Lml1 {
memory_window: 0,
check_frequency: 1,
};
let pool = make_pool(
&[CutMetadata {
iteration_generated: 10,
forward_pass_index: 0,
active_count: 0,
last_active_iter: 10,
domination_count: 0,
}],
&[true],
);
let deact = strategy.select(&pool, &[], 10);
assert!(
deact.indices.is_empty(),
"current-iteration cut must not be deactivated even with memory_window=0"
);
}
#[allow(clippy::cast_possible_truncation)]
fn make_dominated_pool(
intercepts: &[f64],
coefficients: &[Vec<f64>],
active: &[bool],
metadata: &[CutMetadata],
) -> CutPool {
let n = intercepts.len();
let state_dim = coefficients[0].len();
let mut pool = CutPool::new(n, state_dim, 1, 0);
for i in 0..n {
pool.add_cut(0, i as u32, intercepts[i], &coefficients[i]);
pool.metadata[i] = metadata[i].clone();
pool.active[i] = active[i];
}
pool.cached_active_count = active.iter().filter(|&&a| a).count();
pool
}
fn default_meta_at(iter: u64) -> CutMetadata {
CutMetadata {
iteration_generated: iter,
forward_pass_index: 0,
active_count: 0,
last_active_iter: iter,
domination_count: 0,
}
}
fn default_meta_vec(n: usize, iter: u64) -> Vec<CutMetadata> {
(0..n).map(|_| default_meta_at(iter)).collect()
}
#[test]
fn dominated_select_deactivate_dominated() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[1.0, 0.0, 3.0, 0.5, 0.0],
&[
vec![0.0], vec![2.0], vec![-1.0], vec![0.0], vec![0.5], ],
&[true; 5],
&default_meta_vec(5, 1),
);
let states: Vec<f64> = vec![0.0, 1.0, 3.0];
let deact = strategy.select(&pool, &states, 10);
assert_eq!(deact.indices, vec![0, 3, 4]);
}
#[test]
fn dominated_select_partial_domination_retained() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[2.0, 0.0],
&[vec![0.0], vec![2.0]],
&[true, true],
&default_meta_vec(2, 1),
);
let states: Vec<f64> = vec![0.0, 1.0, 3.0];
let deact = strategy.select(&pool, &states, 10);
assert!(
deact.indices.is_empty(),
"cut 0 achieves max at x=0 and x=1, must not be deactivated"
);
}
#[test]
fn dominated_select_none_dominated_when_all_achieve_max() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[5.0, 0.0, 2.0],
&[vec![-2.0], vec![3.0], vec![0.0]],
&[true; 3],
&default_meta_vec(3, 1),
);
let states: Vec<f64> = vec![0.0, 1.0, 3.0];
let deact = strategy.select(&pool, &states, 10);
assert_eq!(
deact.indices,
vec![2],
"only cut 2 (constant 2) should be dominated"
);
}
#[test]
fn dominated_select_empty_states() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[1.0, 2.0],
&[vec![0.0], vec![0.0]],
&[true, true],
&default_meta_vec(2, 1),
);
let deact = strategy.select(&pool, &[], 10);
assert!(
deact.indices.is_empty(),
"empty visited_states must produce empty deactivation set"
);
}
#[test]
fn dominated_select_single_active_cut() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[1.0, 2.0, 3.0],
&[vec![0.0], vec![0.0], vec![0.0]],
&[true, false, false],
&default_meta_vec(3, 1),
);
let states: Vec<f64> = vec![0.0, 1.0];
let deact = strategy.select(&pool, &states, 10);
assert!(
deact.indices.is_empty(),
"single active cut cannot be dominated"
);
}
#[test]
fn dominated_select_current_iteration_excluded() {
let strategy = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let pool = make_dominated_pool(
&[1.0, 5.0],
&[vec![0.0], vec![0.0]],
&[true, true],
&[default_meta_at(10), default_meta_at(1)],
);
let states: Vec<f64> = vec![0.0, 1.0];
let deact = strategy.select(&pool, &states, 10);
assert!(
deact.indices.is_empty(),
"cut from current iteration must not be deactivated even if dominated"
);
}
#[test]
fn aggressiveness_ordering_level1_leq_lml1_leq_dominated() {
let meta = [
CutMetadata {
iteration_generated: 1,
forward_pass_index: 0,
active_count: 0,
last_active_iter: 1,
domination_count: 0,
},
CutMetadata {
iteration_generated: 1,
forward_pass_index: 1,
active_count: 0,
last_active_iter: 2,
domination_count: 0,
},
CutMetadata {
iteration_generated: 1,
forward_pass_index: 2,
active_count: 3,
last_active_iter: 3,
domination_count: 0,
},
CutMetadata {
iteration_generated: 1,
forward_pass_index: 3,
active_count: 5,
last_active_iter: 10,
domination_count: 0,
},
CutMetadata {
iteration_generated: 1,
forward_pass_index: 4,
active_count: 5,
last_active_iter: 10,
domination_count: 0,
},
];
let pool = make_dominated_pool(
&[0.0, 0.0, 1.0, 0.0, 5.0],
&[vec![0.0], vec![0.1], vec![0.0], vec![2.0], vec![-1.0]],
&[true; 5],
&meta,
);
let states: Vec<f64> = vec![0.0, 1.0, 3.0, 5.0];
let l1 = CutSelectionStrategy::Level1 {
threshold: 0,
check_frequency: 1,
};
let deact_l1 = l1.select(&pool, &[], 11);
let lml1 = CutSelectionStrategy::Lml1 {
memory_window: 3,
check_frequency: 1,
};
let deact_lml1 = lml1.select(&pool, &[], 11);
let dom = CutSelectionStrategy::Dominated {
threshold: 0.0,
check_frequency: 1,
};
let deact_dom = dom.select(&pool, &states, 11);
assert!(
deact_l1.indices.len() <= deact_lml1.indices.len(),
"Level1 ({}) should deactivate <= LML1 ({})",
deact_l1.indices.len(),
deact_lml1.indices.len()
);
assert!(
deact_lml1.indices.len() <= deact_dom.indices.len(),
"LML1 ({}) should deactivate <= Dominated ({})",
deact_lml1.indices.len(),
deact_dom.indices.len()
);
}
}