Skip to main content

oximo_solver/
solver.rs

1use oximo_core::{Model, ModelKind, SosType};
2
3use crate::result::SolverResult;
4use crate::status::SolverError;
5
6/// Concrete solver backend.
7///
8/// Backends live in their own crates and the umbrella `oximo` crate
9/// gates them behind cargo features. Implementors translate the
10/// `Model` into their internal representation, solve, and return
11/// a populated [`SolverResult`].
12///
13/// Each backend defines its own [`Options`](Solver::Options) type so users get
14/// LSP autocomplete and compile-time validation on the options that actually
15/// apply. The `oximo_solver` crate ships shared building blocks
16/// ([`UniversalOptions`](crate::UniversalOptions),
17/// [`UniversalOptionsExt`](crate::UniversalOptionsExt))
18/// for backends to compose into their own structs.
19pub trait Solver {
20    /// Backend-specific options struct. Use `()` for solvers without any
21    /// tunables.
22    type Options;
23
24    fn name(&self) -> &str;
25
26    fn supports(&self, kind: ModelKind) -> bool;
27
28    /// Whether this backend can consume native SOS constraints of `sos_type`.
29    fn supports_sos(&self, _sos_type: SosType) -> bool {
30        false
31    }
32
33    /// Whether this backend can consume native indicator constraints.
34    fn supports_indicators(&self) -> bool {
35        false
36    }
37
38    /// Whether this backend can consume all features present in `model`.
39    fn supports_model(&self, model: &Model) -> bool {
40        self.supports(model.kind())
41            && model
42                .sos_constraints()
43                .iter()
44                .filter(|constraint| constraint.active)
45                .all(|constraint| self.supports_sos(constraint.sos_type))
46            && (!model.has_active_indicator_constraints() || self.supports_indicators())
47    }
48
49    /// Solves the given `Model` using this solver.
50    ///
51    /// # Errors
52    ///
53    /// Returns a [`SolverError`] if the model is unsupported or if the solver backend fails.
54    fn solve(&mut self, model: &Model, opts: &Self::Options) -> Result<SolverResult, SolverError>;
55}
56
57#[cfg(test)]
58mod tests {
59    use oximo_core::{SosType, constraint, variable};
60
61    use super::*;
62
63    #[derive(Debug)]
64    struct NoSos;
65
66    impl Solver for NoSos {
67        type Options = ();
68
69        fn name(&self) -> &str {
70            "no-sos"
71        }
72
73        fn supports(&self, kind: ModelKind) -> bool {
74            matches!(kind, ModelKind::LP | ModelKind::MILP)
75        }
76
77        fn solve(&mut self, _model: &Model, _opts: &()) -> Result<SolverResult, SolverError> {
78            unreachable!("capability test solver is never solved")
79        }
80    }
81
82    #[derive(Debug)]
83    struct NativeSos;
84
85    impl Solver for NativeSos {
86        type Options = ();
87
88        fn name(&self) -> &str {
89            "native-sos"
90        }
91
92        fn supports(&self, kind: ModelKind) -> bool {
93            matches!(kind, ModelKind::LP | ModelKind::MILP)
94        }
95
96        fn supports_sos(&self, _sos_type: SosType) -> bool {
97            true
98        }
99
100        fn solve(&mut self, _model: &Model, _opts: &()) -> Result<SolverResult, SolverError> {
101            unreachable!("capability test solver is never solved")
102        }
103    }
104
105    fn sos_model() -> Model {
106        let m = Model::new("capabilities");
107        variable!(m, x);
108        variable!(m, y);
109        constraint!(m, bound, x + y <= 1.0);
110        m.add_sos_constraint("choice", SosType::Sos1, [(x, 1.0), (y, 2.0)]);
111        m
112    }
113
114    #[test]
115    fn supports_model_checks_native_sos_capability() {
116        let model = sos_model();
117        assert!(!NoSos.supports_model(&model));
118        assert!(NativeSos.supports_model(&model));
119
120        let transformed = model
121            .to_reformulated_sos_model(
122                oximo_core::SosReformulationOptions::default().with_fallback_big_m(100.0),
123            )
124            .unwrap();
125        assert!(NoSos.supports_model(&transformed));
126        assert!(NativeSos.supports_model(&transformed));
127    }
128}