use std::fmt::Write as FmtWrite;
use std::path::PathBuf;
use oximo_solver::{HasUniversal, UniversalOptions};
use crate::solver_options::GamsSolverConfig;
#[derive(Clone, Debug, Default)]
pub struct GamsOptions {
pub universal: UniversalOptions,
pub mip_gap: Option<f64>,
pub solver: Option<GamsSolverConfig>,
pub gams_path: Option<PathBuf>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GamsSolver {
AlphaEcp,
Antigone,
Baron,
Cbc,
Conopt,
Copt,
Cplex,
Decis,
Dicopt,
Glpk,
Gurobi,
Guss,
Highs,
Ipopt,
Jams,
Kestrel,
Knitro,
Lindo,
LindoGlobal,
Miles,
Minos,
Mosek,
Nlpec,
OdhCplex,
Path,
QuadMinos,
Reshop,
Sbb,
Scip,
Shot,
Snopt,
Soplex,
Xpress,
Custom(String),
}
impl GamsSolver {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::AlphaEcp => "ALPHAECP",
Self::Antigone => "ANTIGONE",
Self::Baron => "BARON",
Self::Cbc => "CBC",
Self::Conopt => "CONOPT",
Self::Copt => "COPT",
Self::Cplex => "CPLEX",
Self::Decis => "DECIS",
Self::Dicopt => "DICOPT",
Self::Glpk => "GLPK",
Self::Gurobi => "GUROBI",
Self::Guss => "GUSS",
Self::Highs => "HIGHS",
Self::Ipopt => "IPOPT",
Self::Jams => "JAMS",
Self::Kestrel => "KESTREL",
Self::Knitro => "KNITRO",
Self::Lindo => "LINDO",
Self::LindoGlobal => "LINDOGLOBAL",
Self::Miles => "MILES",
Self::Minos => "MINOS",
Self::Mosek => "MOSEK",
Self::Nlpec => "NLPEC",
Self::OdhCplex => "ODHCPLEX",
Self::Path => "PATH",
Self::QuadMinos => "QUADMINOS",
Self::Reshop => "RESHOP",
Self::Sbb => "SBB",
Self::Scip => "SCIP",
Self::Shot => "SHOT",
Self::Snopt => "SNOPT",
Self::Soplex => "SOPLEX",
Self::Xpress => "XPRESS",
Self::Custom(s) => s.as_str(),
}
}
}
impl GamsOptions {
#[must_use]
pub fn mip_gap(mut self, gap: f64) -> Self {
self.mip_gap = Some(gap);
self
}
#[must_use]
pub fn solver(mut self, s: impl Into<GamsSolverConfig>) -> Self {
self.solver = Some(s.into());
self
}
#[must_use]
pub fn gams_path(mut self, p: impl Into<PathBuf>) -> Self {
self.gams_path = Some(p.into());
self
}
}
impl HasUniversal for GamsOptions {
fn universal(&self) -> &UniversalOptions {
&self.universal
}
fn universal_mut(&mut self) -> &mut UniversalOptions {
&mut self.universal
}
}
pub fn write_options(gms: &mut String, o: &GamsOptions, solve_type: &str) {
if let Some(d) = o.universal.time_limit {
writeln!(gms, "option ResLim = {};", d.as_secs_f64()).unwrap();
}
if let Some(g) = o.mip_gap {
writeln!(gms, "option OptCR = {g};").unwrap();
}
if let Some(n) = o.universal.threads {
writeln!(gms, "option threads = {n};").unwrap();
}
if let Some(s) = &o.solver {
writeln!(gms, "option {solve_type} = {};", s.gams_name()).unwrap();
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use oximo_solver::UniversalOptionsExt;
use super::*;
#[test]
fn builder_sets_fields() {
use crate::solver_options::{GamsBaronOptions, GamsSolverConfig};
let o = GamsOptions::default()
.time_limit(Duration::from_secs(45))
.threads(2)
.mip_gap(0.001)
.verbose(true)
.solver(GamsSolverConfig::Baron(GamsBaronOptions::default()))
.gams_path("/opt/gams/gams");
assert_eq!(o.universal.time_limit, Some(Duration::from_secs(45)));
assert_eq!(o.universal.threads, Some(2));
assert_eq!(o.mip_gap, Some(0.001));
assert!(matches!(o.solver, Some(GamsSolverConfig::Baron(_))));
assert_eq!(o.gams_path.as_deref(), Some(std::path::Path::new("/opt/gams/gams")));
}
#[test]
fn write_options_emits_solver_baron() {
use crate::solver_options::{GamsBaronOptions, GamsSolverConfig};
let o = GamsOptions::default().solver(GamsSolverConfig::Baron(GamsBaronOptions::default()));
let mut gms = String::new();
write_options(&mut gms, &o, "MIP");
assert!(gms.contains("option MIP = BARON;"), "got: {gms}");
}
#[test]
fn write_options_emits_custom_solver_verbatim() {
let o = GamsOptions::default().solver(GamsSolver::Custom("MOSEK".into()));
let mut gms = String::new();
write_options(&mut gms, &o, "LP");
assert!(gms.contains("option LP = MOSEK;"), "got: {gms}");
}
#[test]
fn write_options_emits_time_and_gap() {
let o = GamsOptions::default().time_limit(Duration::from_secs(10)).mip_gap(0.05).threads(4);
let mut gms = String::new();
write_options(&mut gms, &o, "MIP");
assert!(gms.contains("option ResLim = 10"));
assert!(gms.contains("option OptCR = 0.05"));
assert!(gms.contains("option threads = 4"));
}
#[test]
fn write_options_empty_for_default() {
let o = GamsOptions::default();
let mut gms = String::new();
write_options(&mut gms, &o, "LP");
assert!(gms.is_empty());
}
}