#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum InflowNonNegativityMethod {
None,
Truncation,
Penalty {
cost: f64,
},
}
impl InflowNonNegativityMethod {
#[must_use]
pub fn has_slack_columns(&self) -> bool {
matches!(self, InflowNonNegativityMethod::Penalty { .. })
}
#[must_use]
pub fn penalty_cost(&self) -> Option<f64> {
match self {
InflowNonNegativityMethod::Penalty { cost } => Some(*cost),
InflowNonNegativityMethod::Truncation | InflowNonNegativityMethod::None => None,
}
}
}
impl From<&cobre_io::config::InflowNonNegativityConfig> for InflowNonNegativityMethod {
fn from(cfg: &cobre_io::config::InflowNonNegativityConfig) -> Self {
match cfg.method.as_str() {
"truncation" => InflowNonNegativityMethod::Truncation,
"penalty" => InflowNonNegativityMethod::Penalty {
cost: cfg.penalty_cost,
},
_ => InflowNonNegativityMethod::None,
}
}
}
#[cfg(test)]
mod tests {
use super::InflowNonNegativityMethod;
use cobre_io::config::InflowNonNegativityConfig;
#[test]
fn none_has_no_slack_columns() {
assert!(!InflowNonNegativityMethod::None.has_slack_columns());
}
#[test]
fn truncation_has_no_slack_columns() {
assert!(!InflowNonNegativityMethod::Truncation.has_slack_columns());
}
#[test]
fn penalty_has_slack_columns() {
assert!(InflowNonNegativityMethod::Penalty { cost: 100.0 }.has_slack_columns());
}
#[test]
fn penalty_cost_for_penalty_variant() {
assert_eq!(
InflowNonNegativityMethod::Penalty { cost: 500.0 }.penalty_cost(),
Some(500.0)
);
}
#[test]
fn penalty_cost_none_for_none_variant() {
assert_eq!(InflowNonNegativityMethod::None.penalty_cost(), None);
}
#[test]
fn truncation_penalty_cost_is_none() {
assert_eq!(InflowNonNegativityMethod::Truncation.penalty_cost(), None);
}
#[test]
fn test_inflow_method_conversion_none() {
let cfg = InflowNonNegativityConfig {
method: "none".to_string(),
penalty_cost: 0.0,
};
assert_eq!(
InflowNonNegativityMethod::from(&cfg),
InflowNonNegativityMethod::None
);
}
#[test]
fn test_inflow_method_conversion_penalty() {
let cfg = InflowNonNegativityConfig {
method: "penalty".to_string(),
penalty_cost: 500.0,
};
assert_eq!(
InflowNonNegativityMethod::from(&cfg),
InflowNonNegativityMethod::Penalty { cost: 500.0 }
);
}
#[test]
fn test_inflow_method_conversion_truncation() {
let cfg = InflowNonNegativityConfig {
method: "truncation".to_string(),
penalty_cost: 0.0,
};
assert_eq!(
InflowNonNegativityMethod::from(&cfg),
InflowNonNegativityMethod::Truncation
);
}
#[test]
fn test_truncation_ignores_penalty_cost() {
let cfg = InflowNonNegativityConfig {
method: "truncation".to_string(),
penalty_cost: 999.0,
};
assert_eq!(
InflowNonNegativityMethod::from(&cfg),
InflowNonNegativityMethod::Truncation
);
}
#[test]
fn test_inflow_method_conversion_unknown_falls_back_to_none() {
let cfg = InflowNonNegativityConfig {
method: "unknown_method".to_string(),
penalty_cost: 100.0,
};
assert_eq!(
InflowNonNegativityMethod::from(&cfg),
InflowNonNegativityMethod::None
);
}
#[test]
fn test_penalty_config_propagation() {
let costs = [0.0, 100.0, 500.0, 1000.0, f64::MAX];
for &expected_cost in &costs {
let cfg = InflowNonNegativityConfig {
method: "penalty".to_string(),
penalty_cost: expected_cost,
};
let method = InflowNonNegativityMethod::from(&cfg);
assert_eq!(method.penalty_cost(), Some(expected_cost));
}
}
}