conjure_core/solver/adaptors/minion/
adaptor.rs
1use regex::Regex;
2use std::collections::HashMap;
3use std::sync::{LazyLock, 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::{self as conjure_ast, Name};
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
42fn parse_name(minion_name: &str) -> Name {
43 static MACHINE_NAME_RE: LazyLock<Regex> =
44 LazyLock::new(|| Regex::new(r"__conjure_machine_name_([0-9]+)").unwrap());
45 static REPRESENTED_NAME_RE: LazyLock<Regex> =
46 LazyLock::new(|| Regex::new(r"__conjure_represented_name##(.*)##(.*)___(.*)").unwrap());
47
48 if let Some(caps) = MACHINE_NAME_RE.captures(minion_name) {
49 conjure_ast::Name::MachineName(caps[1].parse::<i32>().unwrap())
50 } else if let Some(caps) = REPRESENTED_NAME_RE.captures(minion_name) {
51 conjure_ast::Name::RepresentedName(
52 Box::new(parse_name(&caps[1])),
53 caps[2].to_string(),
54 caps[3].to_string(),
55 )
56 } else {
57 conjure_ast::Name::UserName(minion_name.to_string())
58 }
59}
60
61#[allow(clippy::unwrap_used)]
62fn minion_rs_callback(solutions: HashMap<minion_ast::VarName, minion_ast::Constant>) -> bool {
63 *(ANY_SOLUTIONS.lock().unwrap()) = true;
64 let callback = USER_CALLBACK
65 .get_or_init(|| Mutex::new(Box::new(|x| true)))
66 .lock()
67 .unwrap();
68
69 let mut conjure_solutions: HashMap<conjure_ast::Name, conjure_ast::Literal> = HashMap::new();
70 for (minion_name, minion_const) in solutions.into_iter() {
71 let conjure_const = match minion_const {
72 minion_ast::Constant::Bool(x) => conjure_ast::Literal::Bool(x),
73 minion_ast::Constant::Integer(x) => conjure_ast::Literal::Int(x),
74 _ => todo!(),
75 };
76
77 let conjure_name = parse_name(&minion_name);
78 conjure_solutions.insert(conjure_name, conjure_const);
79 }
80
81 let continue_search = (**callback)(conjure_solutions);
82 if !continue_search {
83 *(USER_TERMINATED.lock().unwrap()) = true;
84 }
85
86 continue_search
87}
88
89impl private::Sealed for Minion {}
90
91impl Minion {
92 pub fn new() -> Minion {
93 Minion {
94 __non_constructable: private::Internal,
95 model: None,
96 }
97 }
98}
99
100impl Default for Minion {
101 fn default() -> Self {
102 Minion::new()
103 }
104}
105
106impl SolverAdaptor for Minion {
107 #[allow(clippy::unwrap_used)]
108 fn solve(
109 &mut self,
110 callback: SolverCallback,
111 _: private::Internal,
112 ) -> Result<SolveSuccess, SolverError> {
113 #[allow(clippy::unwrap_used)]
116 let mut minion_lock = MINION_LOCK.lock().unwrap();
117
118 #[allow(clippy::unwrap_used)]
119 let mut user_callback = USER_CALLBACK
120 .get_or_init(|| Mutex::new(Box::new(|x| true)))
121 .lock()
122 .unwrap();
123 *user_callback = callback;
124 drop(user_callback); run_minion(
128 self.model.clone().expect("STATE MACHINE ERR"),
129 minion_rs_callback,
130 )
131 .map_err(|err| match err {
132 MinionError::RuntimeError(x) => Runtime(format!("{:#?}", x)),
133 MinionError::Other(x) => Runtime(format!("{:#?}", x)),
134 MinionError::NotImplemented(x) => RuntimeNotImplemented(x),
135 x => Runtime(format!("unknown minion_rs error: {:#?}", x)),
136 })?;
137
138 let mut status = Complete(HasSolutions);
139 if *(USER_TERMINATED.lock()).unwrap() {
140 status = Incomplete(UserTerminated);
141 } else if *(ANY_SOLUTIONS.lock()).unwrap() {
142 status = Complete(NoSolutions);
143 }
144 Ok(SolveSuccess {
145 stats: get_solver_stats(),
146 status,
147 })
148 }
149
150 fn solve_mut(
151 &mut self,
152 callback: SolverMutCallback,
153 _: private::Internal,
154 ) -> Result<SolveSuccess, SolverError> {
155 Err(OpNotImplemented("solve_mut".into()))
156 }
157
158 fn load_model(&mut self, model: ConjureModel, _: private::Internal) -> Result<(), SolverError> {
159 self.model = Some(model_to_minion(model)?);
160 Ok(())
161 }
162
163 fn get_family(&self) -> SolverFamily {
164 SolverFamily::Minion
165 }
166
167 fn get_name(&self) -> Option<String> {
168 Some("Minion".to_owned())
169 }
170}
171
172#[allow(clippy::unwrap_used)]
173fn get_solver_stats() -> SolverStats {
174 SolverStats {
175 nodes: get_from_table("Nodes".into()).map(|x| x.parse::<u64>().unwrap()),
176 ..Default::default()
177 }
178}