#![allow(clippy::float_cmp)]
use oximo_core::prelude::*;
#[test]
fn classifies_lp() {
let m = Model::new("lp");
let x = m.var("x").lb(0.0).build();
m.constraint("c", x.le(10.0));
m.minimize(x);
assert_eq!(m.kind(), ModelKind::LP);
}
#[test]
fn classifies_milp() {
let m = Model::new("milp");
let x = m.var("x").lb(0.0).ub(1.0).integer().build();
m.constraint("c", x.le(1.0));
m.minimize(x);
assert_eq!(m.kind(), ModelKind::MILP);
}
#[test]
fn classifies_qp() {
let m = Model::new("qp");
let x = m.var("x").lb(0.0).build();
m.minimize(x.powi(2));
assert_eq!(m.kind(), ModelKind::QP);
}
#[test]
fn classifies_miqp() {
let m = Model::new("miqp");
let x = m.var("x").lb(0.0).build();
let y = m.var("y").lb(0.0).ub(1.0).integer().build();
m.minimize(x * y);
assert_eq!(m.kind(), ModelKind::MIQP);
}
#[test]
fn quadratic_constraint_classifies_qp() {
let m = Model::new("qp_con");
let x = m.var("x").lb(0.0).build();
m.constraint("c", x.powi(2).le(4.0));
m.minimize(x);
assert_eq!(m.kind(), ModelKind::QP);
}
#[test]
fn classifies_nlp() {
let m = Model::new("nlp");
let x = m.var("x").lb(0.0).build();
m.minimize(x.powi(3));
assert_eq!(m.kind(), ModelKind::NLP);
}
#[test]
fn classifies_minlp_with_division() {
let m = Model::new("minlp_div");
let x = m.var("x").lb(1.0).build();
let y = m.var("y").lb(0.0).ub(1.0).integer().build();
m.minimize(x / y);
assert_eq!(m.kind(), ModelKind::MINLP);
}
#[test]
fn variable_count_matches_register() {
let m = Model::new("vars");
let _ = m.var("x").build();
let _ = m.var("y").build();
let _ = m.var("z").build();
assert_eq!(m.num_variables(), 3);
}
#[test]
fn indexed_var_creates_named_scalars() {
let m = Model::new("net");
let nodes = Set::range(0..3);
let flow = m.indexed_var("flow", &nodes).lb(0.0).build();
assert_eq!(flow.len(), 3);
assert!(m.variable_id("flow[0]").is_some());
assert!(m.variable_id("flow[2]").is_some());
}
#[test]
fn kind_caches_and_invalidates() {
let m = Model::new("cache");
let x = m.var("x").lb(0.0).build();
m.minimize(x);
assert_eq!(m.kind(), ModelKind::LP);
assert_eq!(m.kind(), ModelKind::LP);
let _ = m.var("y").lb(0.0).integer().build();
assert_eq!(m.kind(), ModelKind::MILP);
m.constraint("c", x.le(10.0));
assert_eq!(m.kind(), ModelKind::MILP);
}
#[test]
fn fix_builder_sets_equal_bounds() {
let m = Model::new("fix_builder");
let _ = m.var("x").lb(0.0).ub(10.0).fix(3.5).build();
let vars = m.variables();
assert_eq!(vars[0].lb, 3.5);
assert_eq!(vars[0].ub, 3.5);
}
#[test]
fn fix_var_mutates_bounds_post_build() {
let m = Model::new("fix_post");
let _ = m.var("x").lb(0.0).ub(10.0).build();
let id = m.variable_id("x").unwrap();
m.fix_var(id, 7.0);
let vars = m.variables();
assert_eq!(vars[0].lb, 7.0);
assert_eq!(vars[0].ub, 7.0);
}
#[test]
fn fix_pins_var_expr_and_indexed_entry() {
let m = Model::new("fix_expr");
let x = m.var("x").lb(0.0).ub(10.0).build();
m.fix(x, 3.0);
let xid = m.variable_id("x").unwrap();
let vars = m.variables();
assert_eq!(vars[xid.index()].lb, 3.0);
assert_eq!(vars[xid.index()].ub, 3.0);
drop(vars);
let keys = Set::strings(["a", "b"]);
let w = m.indexed_var("w", &keys).binary().build();
m.fix(w["a"], 1.0);
let aid = m.variable_id("w[a]").unwrap();
let vars = m.variables();
assert_eq!(vars[aid.index()].lb, 1.0);
assert_eq!(vars[aid.index()].ub, 1.0);
}
#[test]
fn var_id_is_none_for_compound_expr() {
let m = Model::new("var_id");
let x = m.var("x").build();
let y = m.var("y").build();
assert!(x.var_id().is_some());
assert!((x + 1.0).var_id().is_none());
assert!((x + y).var_id().is_none());
assert!((2.0 * x).var_id().is_none());
}
#[test]
fn unfix_var_restores_bounds() {
let m = Model::new("unfix");
let _ = m.var("x").lb(0.0).ub(10.0).build();
let id = m.variable_id("x").unwrap();
m.fix_var(id, 7.0);
m.unfix_var(id, 0.0, 10.0);
let vars = m.variables();
assert_eq!(vars[0].lb, 0.0);
assert_eq!(vars[0].ub, 10.0);
}
#[test]
fn initial_value_stored_on_variable() {
let m = Model::new("init");
let _ = m.var("x").lb(0.0).initial(3.5).build();
let _ = m.var("y").lb(0.0).build();
let vars = m.variables();
assert_eq!(vars[0].initial, Some(3.5));
assert_eq!(vars[1].initial, None);
}
#[test]
fn rhs_expr_folded_into_lhs() {
use oximo_expr::extract_linear;
let m = Model::new("rhs");
let x = m.var("x").build();
let y = m.var("y").build();
m.constraint("c", x.le(y + 3.0));
let cs = m.constraints();
assert_eq!(cs.len(), 1);
assert_eq!(cs[0].rhs, 0.0);
assert_eq!(cs[0].sense, Sense::Le);
let arena = m.arena();
let terms = extract_linear(&arena, cs[0].lhs).expect("linear");
assert_eq!(terms.constant, -3.0);
let mut sorted = terms.coeffs.clone();
sorted.sort_by_key(|(v, _)| v.0);
assert_eq!(sorted[0].1, 1.0);
assert_eq!(sorted[1].1, -1.0);
}