use std::fmt::Write as FmtWrite;
use crate::options::GamsSolver;
#[derive(Clone, Debug)]
pub enum GamsSolverConfig {
Baron(GamsBaronOptions),
Cbc(GamsCbcOptions),
Cplex(GamsCplexOptions),
Gurobi(GamsGurobiOptions),
Highs(GamsHighsOptions),
Ipopt(GamsIpoptOptions),
Knitro(GamsKnitroOptions),
Mosek(GamsMosekOptions),
Scip(GamsScipOptions),
Xpress(GamsXpressOptions),
Named(GamsSolver),
}
impl GamsSolverConfig {
#[must_use]
pub fn gams_name(&self) -> &str {
match self {
Self::Baron(_) => "BARON",
Self::Cbc(_) => "CBC",
Self::Cplex(_) => "CPLEX",
Self::Gurobi(_) => "GUROBI",
Self::Highs(_) => "HIGHS",
Self::Ipopt(_) => "IPOPT",
Self::Knitro(_) => "KNITRO",
Self::Mosek(_) => "MOSEK",
Self::Scip(_) => "SCIP",
Self::Xpress(_) => "XPRESS",
Self::Named(s) => s.name(),
}
}
#[must_use]
pub fn write_opt_file(&self, buf: &mut String) -> bool {
match self {
Self::Baron(o) => o.write(buf),
Self::Cbc(o) => o.write(buf),
Self::Cplex(o) => o.write(buf),
Self::Gurobi(o) => o.write(buf),
Self::Highs(o) => o.write(buf),
Self::Ipopt(o) => o.write(buf),
Self::Knitro(o) => o.write(buf),
Self::Mosek(o) => o.write(buf),
Self::Scip(o) => o.write(buf),
Self::Xpress(o) => o.write(buf),
Self::Named(_) => false,
}
}
}
impl From<GamsSolver> for GamsSolverConfig {
fn from(s: GamsSolver) -> Self {
Self::Named(s)
}
}
fn kv(buf: &mut String, key: &str, val: impl std::fmt::Display) {
writeln!(buf, "{key} {val}").unwrap();
}
fn kv_eq(buf: &mut String, key: &str, val: impl std::fmt::Display) {
writeln!(buf, "{key} = {val}").unwrap();
}
#[derive(Clone, Debug, Default)]
pub struct GamsBaronOptions {
pub max_time: Option<f64>,
pub max_iter: Option<i64>,
pub eps_r: Option<f64>,
pub eps_a: Option<f64>,
pub abs_con_feas_tol: Option<f64>,
pub abs_int_feas_tol: Option<f64>,
pub threads: Option<u32>,
pub num_loc: Option<i32>,
pub num_sol: Option<i32>,
pub cut_off: Option<f64>,
}
impl GamsBaronOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("MaxTime", self.max_time);
w!("MaxIter", self.max_iter);
w!("EpsR", self.eps_r);
w!("EpsA", self.eps_a);
w!("AbsConFeasTol", self.abs_con_feas_tol);
w!("AbsIntFeasTol", self.abs_int_feas_tol);
w!("Threads", self.threads);
w!("NumLoc", self.num_loc);
w!("NumSol", self.num_sol);
w!("CutOff", self.cut_off);
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsCbcPresolve {
On,
Off,
More,
}
#[derive(Clone, Debug)]
pub enum GamsCbcCuts {
Off,
On,
Root,
IfMove,
ForceOn,
}
#[derive(Clone, Debug, Default)]
pub struct GamsCbcOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub node_limit: Option<u64>,
pub presolve: Option<GamsCbcPresolve>,
pub cuts: Option<GamsCbcCuts>,
pub heuristics: Option<bool>,
}
impl GamsCbcOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("threads", self.threads);
w!("optcr", self.mip_rel_gap);
w!("optca", self.mip_abs_gap);
w!("nodlim", self.node_limit);
if let Some(p) = &self.presolve {
kv(
buf,
"presolve",
match p {
GamsCbcPresolve::On => "on",
GamsCbcPresolve::Off => "off",
GamsCbcPresolve::More => "more",
},
);
n += 1;
}
if let Some(c) = &self.cuts {
kv(
buf,
"cuts",
match c {
GamsCbcCuts::Off => "off",
GamsCbcCuts::On => "on",
GamsCbcCuts::Root => "root",
GamsCbcCuts::IfMove => "ifmove",
GamsCbcCuts::ForceOn => "forceOn",
},
);
n += 1;
}
if let Some(h) = self.heuristics {
kv(buf, "heuristics", i32::from(h));
n += 1;
}
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsCplexMipEmphasis {
Balanced,
Feasibility,
Optimality,
BestBound,
HiddenFeasibility,
}
#[derive(Clone, Debug, Default)]
pub struct GamsCplexOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub node_limit: Option<u64>,
pub int_sol_limit: Option<u32>,
pub presolve: Option<bool>,
pub mip_emphasis: Option<GamsCplexMipEmphasis>,
pub node_select: Option<i32>,
pub var_select: Option<i32>,
pub int_tol: Option<f64>,
pub feasibility_tol: Option<f64>,
pub optimality_tol: Option<f64>,
pub lp_method: Option<i32>,
}
impl GamsCplexOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("threads", self.threads);
w!("epgap", self.mip_rel_gap);
w!("epagap", self.mip_abs_gap);
w!("nodelim", self.node_limit);
w!("intsollim", self.int_sol_limit);
if let Some(pre) = self.presolve {
kv(buf, "preind", i32::from(pre));
n += 1;
}
if let Some(e) = &self.mip_emphasis {
kv(
buf,
"mipemphasis",
match e {
GamsCplexMipEmphasis::Balanced => 0,
GamsCplexMipEmphasis::Feasibility => 1,
GamsCplexMipEmphasis::Optimality => 2,
GamsCplexMipEmphasis::BestBound => 3,
GamsCplexMipEmphasis::HiddenFeasibility => 4,
},
);
n += 1;
}
w!("nodesel", self.node_select);
w!("varsel", self.var_select);
w!("epint", self.int_tol);
w!("eprhs", self.feasibility_tol);
w!("epopt", self.optimality_tol);
w!("lpmethod", self.lp_method);
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsGurobiMipFocus {
Balanced,
Feasibility,
Optimality,
BestBound,
}
#[derive(Clone, Debug, Default)]
pub struct GamsGurobiOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub node_limit: Option<u64>,
pub presolve: Option<i32>,
pub cuts: Option<i32>,
pub heuristics: Option<f64>,
pub method: Option<i32>,
pub mip_focus: Option<GamsGurobiMipFocus>,
pub feasibility_tol: Option<f64>,
pub int_feas_tol: Option<f64>,
pub optimality_tol: Option<f64>,
}
impl GamsGurobiOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("threads", self.threads);
w!("mipgap", self.mip_rel_gap);
w!("mipgapabs", self.mip_abs_gap);
w!("nodelimit", self.node_limit);
w!("presolve", self.presolve);
w!("cuts", self.cuts);
w!("heuristics", self.heuristics);
w!("method", self.method);
if let Some(f) = &self.mip_focus {
kv(
buf,
"mipfocus",
match f {
GamsGurobiMipFocus::Balanced => 0,
GamsGurobiMipFocus::Feasibility => 1,
GamsGurobiMipFocus::Optimality => 2,
GamsGurobiMipFocus::BestBound => 3,
},
);
n += 1;
}
w!("feasibilitytol", self.feasibility_tol);
w!("intfeastol", self.int_feas_tol);
w!("optimalitytol", self.optimality_tol);
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsHighsPresolve {
On,
Off,
Choose,
}
#[derive(Clone, Debug)]
pub enum GamsHighsSolver {
Simplex,
Ipm,
Ipx,
Pdlp,
Choose,
}
#[derive(Clone, Debug, Default)]
pub struct GamsHighsOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub node_limit: Option<u64>,
pub presolve: Option<GamsHighsPresolve>,
pub solver: Option<GamsHighsSolver>,
pub primal_feasibility_tol: Option<f64>,
pub dual_feasibility_tol: Option<f64>,
pub optimality_tol: Option<f64>,
}
impl GamsHighsOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv_eq(buf, $key, v);
n += 1;
}
};
}
w!("threads", self.threads);
w!("mip_rel_gap", self.mip_rel_gap);
w!("mip_abs_gap", self.mip_abs_gap);
w!("nodlim", self.node_limit);
if let Some(p) = &self.presolve {
kv_eq(
buf,
"presolve",
match p {
GamsHighsPresolve::On => "on",
GamsHighsPresolve::Off => "off",
GamsHighsPresolve::Choose => "choose",
},
);
n += 1;
}
if let Some(s) = &self.solver {
kv_eq(
buf,
"solver",
match s {
GamsHighsSolver::Simplex => "simplex",
GamsHighsSolver::Ipm => "ipm",
GamsHighsSolver::Ipx => "ipx",
GamsHighsSolver::Pdlp => "pdlp",
GamsHighsSolver::Choose => "choose",
},
);
n += 1;
}
w!("primal_feasibility_tolerance", self.primal_feasibility_tol);
w!("dual_feasibility_tolerance", self.dual_feasibility_tol);
w!("optimality_tolerance", self.optimality_tol);
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsIpoptLinearSolver {
Mumps,
Ma27,
Ma57,
Ma86,
Ma97,
PardisoMkl,
}
#[derive(Clone, Debug)]
pub enum GamsIpoptMuStrategy {
Monotone,
Adaptive,
}
#[derive(Clone, Debug, Default)]
pub struct GamsIpoptOptions {
pub max_iter: Option<u32>,
pub tol: Option<f64>,
pub constr_viol_tol: Option<f64>,
pub dual_inf_tol: Option<f64>,
pub compl_inf_tol: Option<f64>,
pub acceptable_tol: Option<f64>,
pub linear_solver: Option<GamsIpoptLinearSolver>,
pub print_level: Option<u32>,
pub mu_strategy: Option<GamsIpoptMuStrategy>,
}
impl GamsIpoptOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("max_iter", self.max_iter);
w!("tol", self.tol);
w!("constr_viol_tol", self.constr_viol_tol);
w!("dual_inf_tol", self.dual_inf_tol);
w!("compl_inf_tol", self.compl_inf_tol);
w!("acceptable_tol", self.acceptable_tol);
if let Some(ls) = &self.linear_solver {
kv(
buf,
"linear_solver",
match ls {
GamsIpoptLinearSolver::Mumps => "mumps",
GamsIpoptLinearSolver::Ma27 => "ma27",
GamsIpoptLinearSolver::Ma57 => "ma57",
GamsIpoptLinearSolver::Ma86 => "ma86",
GamsIpoptLinearSolver::Ma97 => "ma97",
GamsIpoptLinearSolver::PardisoMkl => "pardisomkl",
},
);
n += 1;
}
w!("print_level", self.print_level);
if let Some(mu) = &self.mu_strategy {
kv(
buf,
"mu_strategy",
match mu {
GamsIpoptMuStrategy::Monotone => "monotone",
GamsIpoptMuStrategy::Adaptive => "adaptive",
},
);
n += 1;
}
n > 0
}
}
#[derive(Clone, Debug)]
pub enum GamsKnitroAlgorithm {
Auto,
InteriorDirect,
InteriorCg,
ActiveSet,
Sqp,
}
#[derive(Clone, Debug, Default)]
pub struct GamsKnitroOptions {
pub algorithm: Option<GamsKnitroAlgorithm>,
pub max_iter: Option<u32>,
pub opt_tol: Option<f64>,
pub opt_tol_abs: Option<f64>,
pub feas_tol: Option<f64>,
pub feas_tol_abs: Option<f64>,
pub threads: Option<u32>,
pub mip_max_nodes: Option<u64>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
}
impl GamsKnitroOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
if let Some(alg) = &self.algorithm {
kv(
buf,
"nlp_algorithm",
match alg {
GamsKnitroAlgorithm::Auto => 0,
GamsKnitroAlgorithm::InteriorDirect => 1,
GamsKnitroAlgorithm::InteriorCg => 2,
GamsKnitroAlgorithm::ActiveSet => 3,
GamsKnitroAlgorithm::Sqp => 4,
},
);
n += 1;
}
w!("maxit", self.max_iter);
w!("opttol", self.opt_tol);
w!("opttol_abs", self.opt_tol_abs);
w!("feastol", self.feas_tol);
w!("feastol_abs", self.feas_tol_abs);
w!("threads", self.threads);
w!("mip_maxnodes", self.mip_max_nodes);
w!("mip_opt_gap_rel", self.mip_rel_gap);
w!("mip_opt_gap_abs", self.mip_abs_gap);
n > 0
}
}
#[derive(Clone, Debug, Default)]
pub struct GamsMosekOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub max_relaxations: Option<i64>,
pub max_branches: Option<i64>,
pub primal_feas_tol: Option<f64>,
pub dual_feas_tol: Option<f64>,
pub mio_feas_tol: Option<f64>,
pub int_relax_tol: Option<f64>,
}
impl GamsMosekOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("MSK_IPAR_NUM_THREADS", self.threads);
w!("MSK_DPAR_MIO_TOL_REL_GAP", self.mip_rel_gap);
w!("MSK_DPAR_MIO_TOL_ABS_GAP", self.mip_abs_gap);
w!("MSK_IPAR_MIO_MAX_NUM_RELAXS", self.max_relaxations);
w!("MSK_IPAR_MIO_MAX_NUM_BRANCHES", self.max_branches);
w!("MSK_DPAR_INTPNT_TOL_PFEAS", self.primal_feas_tol);
w!("MSK_DPAR_INTPNT_TOL_DFEAS", self.dual_feas_tol);
w!("MSK_DPAR_MIO_TOL_FEAS", self.mio_feas_tol);
w!("MSK_DPAR_MIO_TOL_ABS_RELAX_INT", self.int_relax_tol);
n > 0
}
}
#[derive(Clone, Debug, Default)]
pub struct GamsScipOptions {
pub node_limit: Option<i64>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub sol_limit: Option<u32>,
pub feas_tol: Option<f64>,
pub dual_feas_tol: Option<f64>,
pub presolve_rounds: Option<i32>,
pub sep_rounds_root: Option<u32>,
}
impl GamsScipOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv_eq(buf, $key, v);
n += 1;
}
};
}
w!("limits/nodes", self.node_limit);
w!("limits/gap", self.mip_rel_gap);
w!("limits/gapabs", self.mip_abs_gap);
w!("limits/solutions", self.sol_limit);
w!("numerics/feastol", self.feas_tol);
w!("numerics/dualfeastol", self.dual_feas_tol);
w!("presolving/maxrounds", self.presolve_rounds);
w!("separating/maxroundsroot", self.sep_rounds_root);
n > 0
}
}
#[derive(Clone, Debug, Default)]
pub struct GamsXpressOptions {
pub threads: Option<u32>,
pub mip_rel_gap: Option<f64>,
pub mip_abs_gap: Option<f64>,
pub node_limit: Option<u64>,
pub presolve: Option<bool>,
pub cut_strategy: Option<i32>,
pub feas_tol: Option<f64>,
pub optimality_tol: Option<f64>,
pub mip_tol: Option<f64>,
pub lp_algorithm: Option<i32>,
}
impl GamsXpressOptions {
fn write(&self, buf: &mut String) -> bool {
let mut n = 0usize;
macro_rules! w {
($key:expr, $opt:expr) => {
if let Some(v) = $opt {
kv(buf, $key, v);
n += 1;
}
};
}
w!("threads", self.threads);
w!("mipRelStop", self.mip_rel_gap);
w!("mipAbsStop", self.mip_abs_gap);
w!("maxNode", self.node_limit);
if let Some(p) = self.presolve {
kv(buf, "presolve", i32::from(p));
n += 1;
}
w!("cutStrategy", self.cut_strategy);
w!("feasTol", self.feas_tol);
w!("optimalityTol", self.optimality_tol);
w!("mipTol", self.mip_tol);
w!("defaultAlg", self.lp_algorithm);
n > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::GamsSolver;
#[test]
fn baron_writes_space_separated() {
let cfg = GamsSolverConfig::Baron(GamsBaronOptions {
threads: Some(4),
eps_r: Some(0.01),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("Threads 4\n"), "got: {buf}");
assert!(buf.contains("EpsR 0.01\n"), "got: {buf}");
}
#[test]
fn highs_writes_eq_separated() {
let cfg = GamsSolverConfig::Highs(GamsHighsOptions {
mip_rel_gap: Some(0.05),
threads: Some(2),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("mip_rel_gap = 0.05\n"), "got: {buf}");
assert!(buf.contains("threads = 2\n"), "got: {buf}");
}
#[test]
fn highs_presolve_and_solver_enum() {
let cfg = GamsSolverConfig::Highs(GamsHighsOptions {
presolve: Some(GamsHighsPresolve::Off),
solver: Some(GamsHighsSolver::Simplex),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("presolve = off\n"), "got: {buf}");
assert!(buf.contains("solver = simplex\n"), "got: {buf}");
}
#[test]
fn scip_writes_eq_separated() {
let cfg = GamsSolverConfig::Scip(GamsScipOptions {
mip_rel_gap: Some(0.01),
node_limit: Some(1000),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("limits/gap = 0.01\n"), "got: {buf}");
assert!(buf.contains("limits/nodes = 1000\n"), "got: {buf}");
}
#[test]
fn gurobi_writes_space_separated() {
let cfg = GamsSolverConfig::Gurobi(GamsGurobiOptions {
threads: Some(8),
mip_focus: Some(GamsGurobiMipFocus::Feasibility),
presolve: Some(2),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("threads 8\n"), "got: {buf}");
assert!(buf.contains("mipfocus 1\n"), "got: {buf}");
assert!(buf.contains("presolve 2\n"), "got: {buf}");
}
#[test]
fn cplex_bool_as_int_and_emphasis() {
let cfg = GamsSolverConfig::Cplex(GamsCplexOptions {
presolve: Some(false),
mip_emphasis: Some(GamsCplexMipEmphasis::Feasibility),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("preind 0\n"), "got: {buf}");
assert!(buf.contains("mipemphasis 1\n"), "got: {buf}");
}
#[test]
fn cbc_enum_options() {
let cfg = GamsSolverConfig::Cbc(GamsCbcOptions {
presolve: Some(GamsCbcPresolve::On),
cuts: Some(GamsCbcCuts::Root),
heuristics: Some(true),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("presolve on\n"), "got: {buf}");
assert!(buf.contains("cuts root\n"), "got: {buf}");
assert!(buf.contains("heuristics 1\n"), "got: {buf}");
}
#[test]
fn ipopt_string_options() {
let cfg = GamsSolverConfig::Ipopt(GamsIpoptOptions {
linear_solver: Some(GamsIpoptLinearSolver::Ma57),
mu_strategy: Some(GamsIpoptMuStrategy::Adaptive),
max_iter: Some(500),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("linear_solver ma57\n"), "got: {buf}");
assert!(buf.contains("mu_strategy adaptive\n"), "got: {buf}");
assert!(buf.contains("max_iter 500\n"), "got: {buf}");
}
#[test]
fn knitro_algorithm_enum() {
let cfg = GamsSolverConfig::Knitro(GamsKnitroOptions {
algorithm: Some(GamsKnitroAlgorithm::Sqp),
mip_rel_gap: Some(0.001),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("nlp_algorithm 4\n"), "got: {buf}");
assert!(buf.contains("mip_opt_gap_rel 0.001\n"), "got: {buf}");
}
#[test]
fn mosek_long_key_names() {
let cfg = GamsSolverConfig::Mosek(GamsMosekOptions {
threads: Some(2),
mip_rel_gap: Some(1e-4),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("MSK_IPAR_NUM_THREADS 2\n"), "got: {buf}");
assert!(buf.contains("MSK_DPAR_MIO_TOL_REL_GAP 0.0001\n"), "got: {buf}");
}
#[test]
fn xpress_bool_presolve_and_gap() {
let cfg = GamsSolverConfig::Xpress(GamsXpressOptions {
presolve: Some(false),
mip_rel_gap: Some(0.02),
..Default::default()
});
let mut buf = String::new();
assert!(cfg.write_opt_file(&mut buf));
assert!(buf.contains("presolve 0\n"), "got: {buf}");
assert!(buf.contains("mipRelStop 0.02\n"), "got: {buf}");
}
#[test]
fn empty_options_writes_nothing() {
let cfg = GamsSolverConfig::Baron(GamsBaronOptions::default());
let mut buf = String::new();
assert!(!cfg.write_opt_file(&mut buf));
assert!(buf.is_empty());
}
#[test]
fn named_writes_nothing() {
let cfg = GamsSolverConfig::Named(GamsSolver::Baron);
let mut buf = String::new();
assert!(!cfg.write_opt_file(&mut buf));
assert!(buf.is_empty());
}
#[test]
fn gams_name_matches_variant() {
assert_eq!(GamsSolverConfig::Baron(GamsBaronOptions::default()).gams_name(), "BARON");
assert_eq!(GamsSolverConfig::Cbc(GamsCbcOptions::default()).gams_name(), "CBC");
assert_eq!(GamsSolverConfig::Cplex(GamsCplexOptions::default()).gams_name(), "CPLEX");
assert_eq!(GamsSolverConfig::Gurobi(GamsGurobiOptions::default()).gams_name(), "GUROBI");
assert_eq!(GamsSolverConfig::Highs(GamsHighsOptions::default()).gams_name(), "HIGHS");
assert_eq!(GamsSolverConfig::Ipopt(GamsIpoptOptions::default()).gams_name(), "IPOPT");
assert_eq!(GamsSolverConfig::Knitro(GamsKnitroOptions::default()).gams_name(), "KNITRO");
assert_eq!(GamsSolverConfig::Mosek(GamsMosekOptions::default()).gams_name(), "MOSEK");
assert_eq!(GamsSolverConfig::Scip(GamsScipOptions::default()).gams_name(), "SCIP");
assert_eq!(GamsSolverConfig::Xpress(GamsXpressOptions::default()).gams_name(), "XPRESS");
assert_eq!(GamsSolverConfig::Named(GamsSolver::Cplex).gams_name(), "CPLEX");
assert_eq!(
GamsSolverConfig::Named(GamsSolver::Custom("MYMIP".into())).gams_name(),
"MYMIP"
);
}
#[test]
fn from_gams_solver_becomes_named() {
let cfg: GamsSolverConfig = GamsSolver::Gurobi.into();
assert!(matches!(cfg, GamsSolverConfig::Named(GamsSolver::Gurobi)));
assert_eq!(cfg.gams_name(), "GUROBI");
}
}