1use highs::Model as HighsModel;
2use oximo_solver::{HasUniversal, UniversalOptions};
3
4#[derive(Clone, Debug, Default)]
6pub struct HighsOptions {
7 pub universal: UniversalOptions,
8 pub mip_gap: Option<f64>,
9 pub presolve: Option<HighsPresolve>,
10 pub method: Option<HighsMethod>,
11 pub parallel: Option<bool>,
12}
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum HighsPresolve {
17 Off,
18 On,
19 Auto,
20}
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum HighsMethod {
25 Choose,
27 Simplex,
28 Ipm,
30 PdLp,
32}
33
34impl HighsOptions {
35 #[must_use]
36 pub fn mip_gap(mut self, gap: f64) -> Self {
37 self.mip_gap = Some(gap);
38 self
39 }
40
41 #[must_use]
42 pub fn presolve(mut self, p: HighsPresolve) -> Self {
43 self.presolve = Some(p);
44 self
45 }
46
47 #[must_use]
48 pub fn method(mut self, m: HighsMethod) -> Self {
49 self.method = Some(m);
50 self
51 }
52
53 #[must_use]
54 pub fn parallel(mut self, on: bool) -> Self {
55 self.parallel = Some(on);
56 self
57 }
58}
59
60impl HasUniversal for HighsOptions {
61 fn universal(&self) -> &UniversalOptions {
62 &self.universal
63 }
64
65 fn universal_mut(&mut self) -> &mut UniversalOptions {
66 &mut self.universal
67 }
68}
69
70pub(crate) fn apply(model: &mut HighsModel, o: &HighsOptions) {
72 if let Some(d) = o.universal.time_limit {
73 model.set_option("time_limit", d.as_secs_f64());
74 }
75 if let Some(n) = o.universal.threads {
76 model.set_option("threads", i32::try_from(n).unwrap_or(i32::MAX));
77 }
78 if let Some(b) = o.universal.verbose {
79 model.set_option("output_flag", b);
80 model.set_option("log_to_console", b);
81 }
82 if let Some(g) = o.mip_gap {
83 model.set_option("mip_rel_gap", g);
84 }
85 if let Some(p) = o.presolve {
86 model.set_option("presolve", presolve_str(p));
87 }
88 if let Some(m) = o.method {
89 model.set_option("solver", method_str(m));
90 }
91 if let Some(p) = o.parallel {
92 model.set_option("parallel", if p { "on" } else { "off" });
93 }
94}
95
96fn presolve_str(p: HighsPresolve) -> &'static str {
97 match p {
98 HighsPresolve::Off => "off",
99 HighsPresolve::On => "on",
100 HighsPresolve::Auto => "choose",
101 }
102}
103
104fn method_str(m: HighsMethod) -> &'static str {
105 match m {
106 HighsMethod::Choose => "choose",
107 HighsMethod::Simplex => "simplex",
108 HighsMethod::Ipm => "ipm",
109 HighsMethod::PdLp => "pdlp",
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use std::time::Duration;
116
117 use highs::{RowProblem, Sense as HighsSense};
118 use oximo_solver::UniversalOptionsExt;
119
120 use super::*;
121
122 fn empty_highs_model() -> HighsModel {
123 RowProblem::default().optimise(HighsSense::Minimise)
124 }
125
126 #[test]
127 fn builder_sets_all_fields() {
128 let o = HighsOptions::default()
129 .time_limit(Duration::from_secs(30))
130 .threads(8)
131 .verbose(true)
132 .mip_gap(0.01)
133 .presolve(HighsPresolve::Off)
134 .method(HighsMethod::Ipm)
135 .parallel(true);
136 assert_eq!(o.universal.time_limit, Some(Duration::from_secs(30)));
137 assert_eq!(o.universal.threads, Some(8));
138 assert_eq!(o.universal.verbose, Some(true));
139 assert_eq!(o.mip_gap, Some(0.01));
140 assert_eq!(o.presolve, Some(HighsPresolve::Off));
141 assert_eq!(o.method, Some(HighsMethod::Ipm));
142 assert_eq!(o.parallel, Some(true));
143 }
144
145 #[test]
146 fn apply_default_does_not_panic() {
147 let mut m = empty_highs_model();
148 apply(&mut m, &HighsOptions::default());
149 }
150
151 #[test]
152 fn apply_all_options_does_not_panic() {
153 let mut m = empty_highs_model();
154 let o = HighsOptions::default()
155 .time_limit(Duration::from_secs(10))
156 .threads(1)
157 .verbose(false)
158 .mip_gap(0.01)
159 .presolve(HighsPresolve::Off)
160 .method(HighsMethod::Simplex)
161 .parallel(false);
162 apply(&mut m, &o);
163 }
164
165 #[test]
166 fn apply_every_method_variant() {
167 for method in
168 [HighsMethod::Choose, HighsMethod::Simplex, HighsMethod::Ipm, HighsMethod::PdLp]
169 {
170 let mut m = empty_highs_model();
171 apply(&mut m, &HighsOptions::default().method(method));
172 }
173 }
174
175 #[test]
176 fn apply_every_presolve_variant() {
177 for presolve in [HighsPresolve::Off, HighsPresolve::On, HighsPresolve::Auto] {
178 let mut m = empty_highs_model();
179 apply(&mut m, &HighsOptions::default().presolve(presolve));
180 }
181 }
182}