conjure_core/solver/adaptors/minion/
adaptor.rs1use regex::Regex;
2use std::collections::HashMap;
3use std::sync::{Mutex, OnceLock};
4
5use minion_ast::Model as MinionModel;
6use minion_rs::ast as minion_ast;
7use minion_rs::error::MinionError;
8use minion_rs::{get_from_table, run_minion};
9
10use crate::ast as conjure_ast;
11use crate::solver::SolverCallback;
12use crate::solver::SolverFamily;
13use crate::solver::SolverMutCallback;
14use crate::stats::SolverStats;
15use crate::Model as ConjureModel;
16
17use crate::solver::model_modifier::NotModifiable;
18use crate::solver::private;
19use crate::solver::SearchComplete::*;
20use crate::solver::SearchIncomplete::*;
21use crate::solver::SearchStatus::*;
22use crate::solver::SolveSuccess;
23use crate::solver::SolverAdaptor;
24use crate::solver::SolverError;
25use crate::solver::SolverError::*;
26
27use super::parse_model::model_to_minion;
28
29pub struct Minion {
33 __non_constructable: private::Internal,
34 model: Option<MinionModel>,
35}
36
37static MINION_LOCK: Mutex<()> = Mutex::new(());
38static USER_CALLBACK: OnceLock<Mutex<SolverCallback>> = OnceLock::new();
39static ANY_SOLUTIONS: Mutex<bool> = Mutex::new(false);
40static USER_TERMINATED: Mutex<bool> = Mutex::new(false);
41
42#[allow(clippy::unwrap_used)]
43fn minion_rs_callback(solutions: HashMap<minion_ast::VarName, minion_ast::Constant>) -> bool {
44 *(ANY_SOLUTIONS.lock().unwrap()) = true;
45 let callback = USER_CALLBACK
46 .get_or_init(|| Mutex::new(Box::new(|x| true)))
47 .lock()
48 .unwrap();
49
50 let mut conjure_solutions: HashMap<conjure_ast::Name, conjure_ast::Literal> = HashMap::new();
51 let machine_name_re = Regex::new(r"__conjure_machine_name_([0-9]+)").unwrap();
52 for (minion_name, minion_const) in solutions.into_iter() {
53 let conjure_const = match minion_const {
54 minion_ast::Constant::Bool(x) => conjure_ast::Literal::Bool(x),
55 minion_ast::Constant::Integer(x) => conjure_ast::Literal::Int(x),
56 _ => todo!(),
57 };
58
59 let conjure_name = if let Some(caps) = machine_name_re.captures(&minion_name) {
60 conjure_ast::Name::MachineName(caps[1].parse::<i32>().unwrap())
61 } else {
62 conjure_ast::Name::UserName(minion_name)
63 };
64
65 conjure_solutions.insert(conjure_name, conjure_const);
66 }
67
68 let continue_search = (**callback)(conjure_solutions);
69 if !continue_search {
70 *(USER_TERMINATED.lock().unwrap()) = true;
71 }
72
73 continue_search
74}
75
76impl private::Sealed for Minion {}
77
78impl Minion {
79 pub fn new() -> Minion {
80 Minion {
81 __non_constructable: private::Internal,
82 model: None,
83 }
84 }
85}
86
87impl Default for Minion {
88 fn default() -> Self {
89 Minion::new()
90 }
91}
92
93impl SolverAdaptor for Minion {
94 #[allow(clippy::unwrap_used)]
95 fn solve(
96 &mut self,
97 callback: SolverCallback,
98 _: private::Internal,
99 ) -> Result<SolveSuccess, SolverError> {
100 #[allow(clippy::unwrap_used)]
103 let mut minion_lock = MINION_LOCK.lock().unwrap();
104
105 #[allow(clippy::unwrap_used)]
106 let mut user_callback = USER_CALLBACK
107 .get_or_init(|| Mutex::new(Box::new(|x| true)))
108 .lock()
109 .unwrap();
110 *user_callback = callback;
111 drop(user_callback); run_minion(
115 self.model.clone().expect("STATE MACHINE ERR"),
116 minion_rs_callback,
117 )
118 .map_err(|err| match err {
119 MinionError::RuntimeError(x) => Runtime(format!("{:#?}", x)),
120 MinionError::Other(x) => Runtime(format!("{:#?}", x)),
121 MinionError::NotImplemented(x) => RuntimeNotImplemented(x),
122 x => Runtime(format!("unknown minion_rs error: {:#?}", x)),
123 })?;
124
125 let mut status = Complete(HasSolutions);
126 if *(USER_TERMINATED.lock()).unwrap() {
127 status = Incomplete(UserTerminated);
128 } else if *(ANY_SOLUTIONS.lock()).unwrap() {
129 status = Complete(NoSolutions);
130 }
131 Ok(SolveSuccess {
132 stats: get_solver_stats(),
133 status,
134 })
135 }
136
137 fn solve_mut(
138 &mut self,
139 callback: SolverMutCallback,
140 _: private::Internal,
141 ) -> Result<SolveSuccess, SolverError> {
142 Err(OpNotImplemented("solve_mut".into()))
143 }
144
145 fn load_model(&mut self, model: ConjureModel, _: private::Internal) -> Result<(), SolverError> {
146 self.model = Some(model_to_minion(model)?);
147 Ok(())
148 }
149
150 fn get_family(&self) -> SolverFamily {
151 SolverFamily::Minion
152 }
153
154 fn get_name(&self) -> Option<String> {
155 Some("Minion".to_owned())
156 }
157}
158
159#[allow(clippy::unwrap_used)]
160fn get_solver_stats() -> SolverStats {
161 SolverStats {
162 nodes: get_from_table("Nodes".into()).map(|x| x.parse::<u64>().unwrap()),
163 ..Default::default()
164 }
165}