1#![allow(unreachable_patterns)]
2#![allow(unsafe_op_in_unsafe_fn)]
3
4use std::{
5 collections::HashMap,
6 ffi::{CStr, CString, c_char, c_void},
7};
8
9use anyhow::anyhow;
10
11use crate::{
12 ast::{Constant, Constraint, Model, Var, VarDomain, VarName},
13 error::{MinionError, check_minion_result},
14 scoped_ptr::Scoped,
15};
16use crate::{
17 ast::{ShortTuple, Tuple},
18 ffi::{self},
19};
20
21pub type Callback<'a> = Box<dyn FnMut(HashMap<VarName, Constant>) -> bool + 'a>;
100
101pub type MidSearchCallback<'a> =
105 Box<dyn FnMut(&mut MidSearchContext<'_>, HashMap<VarName, Constant>) -> bool + 'a>;
106
107pub struct MidSearchContext<'a> {
118 ctx: *mut ffi::MinionContext,
119 instance: *mut ffi::ProbSpec_CSPInstance,
120 midsearch_vars: &'a mut Vec<VarName>,
121 _not_send: std::marker::PhantomData<*mut ()>,
122}
123
124impl MidSearchContext<'_> {
125 pub fn add_var(&mut self, name: &str, domain: VarDomain) -> Result<(), MinionError> {
132 let c_name = CString::new(name)
133 .map_err(|_| anyhow!("Variable name {:?} contains a null character.", name))?;
134 unsafe {
135 match domain {
136 VarDomain::Bool => {
137 check_minion_result(ffi::minion_newVarMidsearch(
138 self.ctx,
139 self.instance,
140 c_name.as_ptr() as *mut c_char,
141 ffi::VariableType_VAR_BOOL,
142 0,
143 1,
144 ))?;
145 }
146 VarDomain::Bound(a, b) => {
147 check_minion_result(ffi::minion_newVarMidsearch(
148 self.ctx,
149 self.instance,
150 c_name.as_ptr() as *mut c_char,
151 ffi::VariableType_VAR_BOUND,
152 a,
153 b,
154 ))?;
155 }
156 VarDomain::Discrete(a, b) => {
157 check_minion_result(ffi::minion_newVarMidsearch(
158 self.ctx,
159 self.instance,
160 c_name.as_ptr() as *mut c_char,
161 ffi::VariableType_VAR_DISCRETE,
162 a,
163 b,
164 ))?;
165 }
166 VarDomain::SparseBound(ref vals) => {
167 let raw = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
168 for v in vals {
169 ffi::vec_int_push_back(raw.ptr, *v);
170 }
171 check_minion_result(ffi::minion_newSparseBoundVarMidsearch(
172 self.ctx,
173 self.instance,
174 c_name.as_ptr() as *mut c_char,
175 raw.ptr,
176 ))?;
177 }
178 x => return Err(MinionError::NotImplemented(format!("{x:?}"))),
179 }
180 }
181 self.midsearch_vars.push(name.to_owned());
182 Ok(())
183 }
184
185 pub fn add_constraint(&mut self, constraint: Constraint) -> Result<(), MinionError> {
190 unsafe {
191 let ct = get_constraint_type(&constraint)?;
192 let raw = Scoped::new(ffi::constraint_new(ct), |x| ffi::constraint_free(x as _));
193 constraint_add_args(self.instance, raw.ptr, &constraint)?;
194 check_minion_result(ffi::minion_addConstraintMidsearch(
195 self.ctx,
196 self.instance,
197 raw.ptr,
198 ))?;
199 }
200 Ok(())
201 }
202}
203
204struct CallbackState<'a> {
209 callback: MidSearchCallback<'a>,
210 instance: *mut ffi::ProbSpec_CSPInstance,
211 print_vars: Vec<VarName>,
214 midsearch_vars: Vec<VarName>,
218}
219
220pub struct SolverContext {
225 ctx: *mut ffi::MinionContext,
226}
227
228unsafe impl Send for SolverContext {}
232
233impl SolverContext {
234 pub fn get_from_table(&self, key: String) -> Option<String> {
236 unsafe {
237 #[allow(clippy::expect_used)]
238 let c_string = CString::new(key).expect("");
239 let key_ptr = c_string.into_raw();
240 let val_ptr: *mut c_char = ffi::TableOut_get(self.ctx, key_ptr);
241
242 drop(CString::from_raw(key_ptr));
243
244 if val_ptr.is_null() {
245 None
246 } else {
247 #[allow(clippy::unwrap_used)]
248 let res = CStr::from_ptr(val_ptr).to_str().unwrap().to_owned();
249 libc::free(val_ptr as _);
250 Some(res)
251 }
252 }
253 }
254}
255
256impl Drop for SolverContext {
257 fn drop(&mut self) {
258 unsafe {
259 ffi::minion_freeContext(self.ctx);
260 }
261 }
262}
263
264unsafe extern "C" fn run_callback(ctx: *mut ffi::MinionContext, userdata: *mut c_void) -> bool {
267 let state = unsafe { &mut *(userdata as *mut CallbackState<'_>) };
270
271 let mut solutions: HashMap<VarName, Constant> = HashMap::new();
273
274 for (i, var) in state.print_vars.iter().enumerate() {
276 let v: i32 = unsafe { ffi::printMatrix_getValue(ctx, i as _) };
277 solutions.insert(var.clone(), Constant::Integer(v));
278 }
279
280 for var in state.midsearch_vars.iter() {
282 #[allow(clippy::unwrap_used)]
283 let c_name = CString::new(var.clone()).unwrap();
284 let v: i32 = unsafe { ffi::minion_getVarValue(ctx, state.instance, c_name.as_ptr()) };
285 solutions.insert(var.clone(), Constant::Integer(v));
286 }
287
288 let mut midctx = MidSearchContext {
289 ctx,
290 instance: state.instance,
291 midsearch_vars: &mut state.midsearch_vars,
292 _not_send: std::marker::PhantomData,
293 };
294 (state.callback)(&mut midctx, solutions)
295}
296
297#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
306pub enum PropLevel {
307 #[default]
308 None,
309 Gac,
310 SacBounds,
311 Sac,
312 SsacBounds,
313 Ssac,
314}
315
316impl PropLevel {
317 fn to_ffi(self) -> ffi::PropagationType {
318 match self {
319 PropLevel::None => ffi::PropagationType_PropLevel_None,
320 PropLevel::Gac => ffi::PropagationType_PropLevel_GAC,
321 PropLevel::SacBounds => ffi::PropagationType_PropLevel_SACBounds,
322 PropLevel::Sac => ffi::PropagationType_PropLevel_SAC,
323 PropLevel::SsacBounds => ffi::PropagationType_PropLevel_SSACBounds,
324 PropLevel::Ssac => ffi::PropagationType_PropLevel_SSAC,
325 }
326 }
327}
328
329#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
336pub struct Propagation {
337 pub level: PropLevel,
338 pub limit: bool,
339}
340
341impl Propagation {
342 fn to_ffi(self) -> ffi::PropagationLevel {
343 ffi::PropagationLevel {
344 type_: self.level.to_ffi(),
345 limit: self.limit,
346 }
347 }
348
349 pub fn is_default_preprocess(self) -> bool {
350 self.level == PropLevel::None && !self.limit
351 }
352
353 pub fn is_default_prop_node(self) -> bool {
354 self.level == PropLevel::Gac && !self.limit
355 }
356}
357
358#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
366pub enum VarOrder {
367 #[default]
368 Static,
369 Sdf,
370 Srf,
371 Ldf,
372 Original,
373 Wdeg,
374 DomOverWdeg,
375 Conflict,
376}
377
378#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
385pub enum ValOrder {
386 #[default]
387 Ascend,
388 Descend,
389 Random,
390}
391
392#[derive(Clone, Copy, Debug)]
395pub struct RunOptions {
396 pub seed: Option<u32>,
403
404 pub var_order: VarOrder,
406
407 pub val_order: ValOrder,
409
410 pub randomise_order: bool,
418
419 pub preprocess: Propagation,
423
424 pub prop_node: Propagation,
430
431 pub node_limit: u64,
437
438 pub time_limit: Option<TimeLimit>,
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
458pub struct TimeLimit {
459 pub seconds: u32,
460 pub is_cpu_time: bool,
461}
462
463impl Default for RunOptions {
464 fn default() -> Self {
465 Self {
466 seed: None,
467 var_order: VarOrder::default(),
468 val_order: ValOrder::default(),
469 randomise_order: false,
470 preprocess: Propagation {
471 level: PropLevel::None,
472 limit: false,
473 },
474 prop_node: Propagation {
475 level: PropLevel::Gac,
476 limit: false,
477 },
478 node_limit: 0,
479 time_limit: None,
480 }
481 }
482}
483
484impl VarOrder {
485 fn to_ffi(self) -> ffi::VarOrderEnum {
486 match self {
487 VarOrder::Static => ffi::VarOrderEnum_ORDER_STATIC,
488 VarOrder::Sdf => ffi::VarOrderEnum_ORDER_SDF,
489 VarOrder::Srf => ffi::VarOrderEnum_ORDER_SRF,
490 VarOrder::Ldf => ffi::VarOrderEnum_ORDER_LDF,
491 VarOrder::Original => ffi::VarOrderEnum_ORDER_ORIGINAL,
492 VarOrder::Wdeg => ffi::VarOrderEnum_ORDER_WDEG,
493 VarOrder::DomOverWdeg => ffi::VarOrderEnum_ORDER_DOMOVERWDEG,
494 VarOrder::Conflict => ffi::VarOrderEnum_ORDER_CONFLICT,
495 }
496 }
497}
498
499impl ValOrder {
500 fn to_ffi(self) -> ffi::ValOrderEnum {
501 match self {
502 ValOrder::Ascend => ffi::ValOrderEnum_VALORDER_ASCEND,
503 ValOrder::Descend => ffi::ValOrderEnum_VALORDER_DESCEND,
504 ValOrder::Random => ffi::ValOrderEnum_VALORDER_RANDOM,
505 }
506 }
507}
508
509pub fn run_minion(model: Model, callback: Callback<'_>) -> Result<SolverContext, MinionError> {
519 run_minion_with_options(model, RunOptions::default(), callback)
520}
521
522pub fn run_minion_with_options(
525 model: Model,
526 options: RunOptions,
527 mut callback: Callback<'_>,
528) -> Result<SolverContext, MinionError> {
529 run_minion_midsearch_with_options(model, options, Box::new(move |_ctx, sol| callback(sol)))
530}
531
532pub fn run_minion_midsearch(
535 model: Model,
536 callback: MidSearchCallback<'_>,
537) -> Result<SolverContext, MinionError> {
538 run_minion_midsearch_with_options(model, RunOptions::default(), callback)
539}
540
541pub type ParallelCallback<'a> = Box<dyn FnMut(HashMap<VarName, Constant>) -> bool + Send + 'a>;
549
550struct ParallelCallbackState<'a> {
554 callback: std::sync::Mutex<ParallelCallback<'a>>,
555 print_vars: Vec<VarName>,
559}
560
561unsafe extern "C" fn parallel_callback_thunk(
562 ctx: *mut ffi::MinionContext,
563 userdata: *mut c_void,
564) -> bool {
565 let state = unsafe { &*(userdata as *mut ParallelCallbackState<'_>) };
566
567 let mut solutions: HashMap<VarName, Constant> = HashMap::new();
568 for (i, var) in state.print_vars.iter().enumerate() {
569 let v: i32 = unsafe { ffi::printMatrix_getValue(ctx, i as _) };
570 solutions.insert(var.clone(), Constant::Integer(v));
571 }
572
573 #[allow(clippy::unwrap_used)]
577 let mut cb = state.callback.lock().unwrap();
578 cb(solutions)
579}
580
581pub fn run_minion_parallel(
606 num_threads: usize,
607 model: Model,
608 callback: ParallelCallback<'_>,
609) -> Result<(), MinionError> {
610 run_minion_parallel_with_options(num_threads, model, RunOptions::default(), callback)
611}
612
613pub fn run_minion_work_steal(
630 num_threads: usize,
631 model: Model,
632 callback: ParallelCallback<'_>,
633) -> Result<WorkStealStats, MinionError> {
634 run_minion_work_steal_with_options(num_threads, model, RunOptions::default(), callback)
635}
636
637#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
659pub struct WorkStealStats {
660 pub donations: i64,
661 pub items_taken: i64,
662 pub replay_failures: i64,
663 pub total_nodes: i64,
664 pub queue_lock_wait_nanos: i64,
665 pub idle_wait_nanos: i64,
666 pub callback_lock_wait_nanos: i64,
667}
668
669#[allow(clippy::unwrap_used)]
671pub fn run_minion_work_steal_with_options(
672 num_threads: usize,
673 model: Model,
674 options: RunOptions,
675 callback: ParallelCallback<'_>,
676) -> Result<WorkStealStats, MinionError> {
677 if num_threads == 0 {
678 return Err(MinionError::Other(anyhow!(
679 "num_threads must be at least 1"
680 )));
681 }
682 if num_threads > i32::MAX as usize {
683 return Err(MinionError::Other(anyhow!("num_threads is too large")));
684 }
685
686 unsafe {
687 let search_opts = ffi::searchOptions_new();
688 let search_method = ffi::searchMethod_new();
689 let search_instance = ffi::instance_new();
690
691 (*search_opts).randomiseValvarorder = options.randomise_order;
692 (*search_opts).silent = true;
693 (*search_opts).print_solution = false;
694 (*search_opts).sollimit = -1;
695
696 if let Some(seed) = options.seed {
697 #[allow(clippy::useless_conversion)]
700 {
701 (*search_method).randomSeed = seed.into();
702 }
703 }
704 (*search_method).preprocess = options.preprocess.to_ffi();
705 (*search_method).propMethod = options.prop_node.to_ffi();
706 if options.node_limit > 0 {
707 (*search_opts).nodelimit = options.node_limit as _;
708 }
709 if let Some(tl) = options.time_limit {
710 (*search_opts).timeoutActive = true;
711 (*search_opts).time_limit = tl.seconds as _;
712 (*search_opts).time_limit_is_CPUTime = tl.is_cpu_time;
713 }
714
715 let mut print_vars: Vec<VarName> = vec![];
716 let convert_result =
717 convert_model_to_raw(search_instance, &model, &options, &mut print_vars);
718 if let Err(e) = convert_result {
719 ffi::searchMethod_free(search_method);
720 ffi::searchOptions_free(search_opts);
721 ffi::instance_free(search_instance);
722 return Err(e);
723 }
724
725 let state = ParallelCallbackState {
726 callback: std::sync::Mutex::new(callback),
727 print_vars,
728 };
729 let userdata = &state as *const ParallelCallbackState<'_> as *mut c_void;
730
731 let cfg = ffi::MinionThreadConfig {
732 numThreads: num_threads as i32,
733 baseSeed: options.seed.unwrap_or(0),
734 };
735
736 let mut raw_stats = ffi::MinionWorkStealStats {
737 donations: 0,
738 itemsTaken: 0,
739 replayFailures: 0,
740 totalNodes: 0,
741 queueLockWaitNanos: 0,
742 idleWaitNanos: 0,
743 callbackLockWaitNanos: 0,
744 };
745
746 let res = ffi::runMinionWorkSteal(
747 cfg,
748 search_opts,
749 search_method,
750 search_instance,
751 Some(parallel_callback_thunk),
752 userdata,
753 &mut raw_stats,
754 );
755
756 ffi::searchMethod_free(search_method);
757 ffi::searchOptions_free(search_opts);
758 ffi::instance_free(search_instance);
759
760 check_minion_result(res)?;
761 Ok(WorkStealStats {
762 donations: raw_stats.donations,
763 items_taken: raw_stats.itemsTaken,
764 replay_failures: raw_stats.replayFailures,
765 total_nodes: raw_stats.totalNodes,
766 queue_lock_wait_nanos: raw_stats.queueLockWaitNanos,
767 idle_wait_nanos: raw_stats.idleWaitNanos,
768 callback_lock_wait_nanos: raw_stats.callbackLockWaitNanos,
769 })
770 }
771}
772
773#[allow(clippy::unwrap_used)]
777pub fn run_minion_parallel_with_options(
778 num_threads: usize,
779 model: Model,
780 options: RunOptions,
781 callback: ParallelCallback<'_>,
782) -> Result<(), MinionError> {
783 if num_threads == 0 {
784 return Err(MinionError::Other(anyhow!(
785 "num_threads must be at least 1"
786 )));
787 }
788 if num_threads > i32::MAX as usize {
789 return Err(MinionError::Other(anyhow!("num_threads is too large")));
790 }
791
792 unsafe {
793 let search_opts = ffi::searchOptions_new();
794 let search_method = ffi::searchMethod_new();
795 let search_instance = ffi::instance_new();
796
797 (*search_opts).randomiseValvarorder = options.randomise_order;
801 (*search_opts).silent = true;
802 (*search_opts).print_solution = false;
803 (*search_opts).sollimit = -1;
807
808 if let Some(seed) = options.seed {
809 #[allow(clippy::useless_conversion)]
812 {
813 (*search_method).randomSeed = seed.into();
814 }
815 }
816 (*search_method).preprocess = options.preprocess.to_ffi();
817 (*search_method).propMethod = options.prop_node.to_ffi();
818 if options.node_limit > 0 {
819 (*search_opts).nodelimit = options.node_limit as _;
820 }
821 if let Some(tl) = options.time_limit {
822 (*search_opts).timeoutActive = true;
823 (*search_opts).time_limit = tl.seconds as _;
824 (*search_opts).time_limit_is_CPUTime = tl.is_cpu_time;
825 }
826
827 let mut print_vars: Vec<VarName> = vec![];
828 let convert_result =
829 convert_model_to_raw(search_instance, &model, &options, &mut print_vars);
830 if let Err(e) = convert_result {
831 ffi::searchMethod_free(search_method);
832 ffi::searchOptions_free(search_opts);
833 ffi::instance_free(search_instance);
834 return Err(e);
835 }
836
837 let state = ParallelCallbackState {
838 callback: std::sync::Mutex::new(callback),
839 print_vars,
840 };
841 let userdata = &state as *const ParallelCallbackState<'_> as *mut c_void;
842
843 let cfg = ffi::MinionThreadConfig {
844 numThreads: num_threads as i32,
845 baseSeed: options.seed.unwrap_or(0),
846 };
847
848 let res = ffi::runMinionParallel(
849 cfg,
850 search_opts,
851 search_method,
852 search_instance,
853 Some(parallel_callback_thunk),
854 userdata,
855 );
856
857 ffi::searchMethod_free(search_method);
858 ffi::searchOptions_free(search_opts);
859 ffi::instance_free(search_instance);
860
861 check_minion_result(res)?;
862 Ok(())
863 }
864}
865
866#[allow(clippy::unwrap_used)]
868pub fn run_minion_midsearch_with_options(
869 model: Model,
870 options: RunOptions,
871 callback: MidSearchCallback<'_>,
872) -> Result<SolverContext, MinionError> {
873 unsafe {
874 let ctx = ffi::minion_newContext();
875 let search_opts = ffi::searchOptions_new();
876 let search_method = ffi::searchMethod_new();
877 let search_instance = ffi::instance_new();
878
879 (*search_opts).randomiseValvarorder = options.randomise_order;
883 (*search_opts).silent = true;
884 (*search_opts).print_solution = false;
885
886 if let Some(seed) = options.seed {
887 #[allow(clippy::useless_conversion)]
890 {
891 (*search_method).randomSeed = seed.into();
892 }
893 }
894 (*search_method).preprocess = options.preprocess.to_ffi();
895 (*search_method).propMethod = options.prop_node.to_ffi();
896 if options.node_limit > 0 {
897 (*search_opts).nodelimit = options.node_limit as _;
898 }
899 if let Some(tl) = options.time_limit {
900 (*search_opts).timeoutActive = true;
901 (*search_opts).time_limit = tl.seconds as _;
902 (*search_opts).time_limit_is_CPUTime = tl.is_cpu_time;
903 }
904
905 let mut state = CallbackState {
906 callback,
907 instance: search_instance,
908 print_vars: vec![],
909 midsearch_vars: vec![],
910 };
911
912 convert_model_to_raw(search_instance, &model, &options, &mut state.print_vars)?;
913
914 let userdata = &mut state as *mut CallbackState<'_> as *mut c_void;
915 let res = ffi::runMinion(
916 ctx,
917 search_opts,
918 search_method,
919 search_instance,
920 Some(run_callback),
921 userdata,
922 );
923
924 ffi::searchMethod_free(search_method);
925 ffi::searchOptions_free(search_opts);
926 ffi::instance_free(search_instance);
927
928 match check_minion_result(res) {
929 Ok(()) => Ok(SolverContext { ctx }),
930 Err(e) => {
931 ffi::minion_freeContext(ctx);
932 Err(MinionError::from(e))
933 }
934 }
935 }
936}
937
938unsafe fn convert_model_to_raw(
939 instance: *mut ffi::ProbSpec_CSPInstance,
940 model: &Model,
941 options: &RunOptions,
942 print_vars: &mut Vec<VarName>,
943) -> Result<(), MinionError> {
944 let search_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
958
959 for var_name in model.named_variables.get_variable_order() {
961 let c_str = CString::new(var_name.clone()).map_err(|_| {
962 anyhow!(
963 "Variable name {:?} contains a null character.",
964 var_name.clone()
965 )
966 })?;
967
968 let vartype = model
969 .named_variables
970 .get_vartype(var_name.clone())
971 .ok_or(anyhow!("Could not get var type for {:?}", var_name.clone()))?;
972
973 match vartype {
974 VarDomain::Bound(a, b) => {
975 check_minion_result(ffi::minion_newVar(
976 instance,
977 c_str.as_ptr() as *mut c_char,
978 ffi::VariableType_VAR_BOUND,
979 a,
980 b,
981 ))?;
982 }
983 VarDomain::Discrete(a, b) => {
984 check_minion_result(ffi::minion_newVar(
985 instance,
986 c_str.as_ptr() as *mut c_char,
987 ffi::VariableType_VAR_DISCRETE,
988 a,
989 b,
990 ))?;
991 }
992 VarDomain::Bool => {
993 check_minion_result(ffi::minion_newVar(
994 instance,
995 c_str.as_ptr() as *mut c_char,
996 ffi::VariableType_VAR_BOOL,
997 0,
998 1,
999 ))?;
1000 }
1001 VarDomain::SparseBound(ref vals) => {
1002 let raw = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
1003 for v in vals {
1004 ffi::vec_int_push_back(raw.ptr, *v);
1005 }
1006 check_minion_result(ffi::minion_newSparseBoundVar(
1007 instance,
1008 c_str.as_ptr() as *mut c_char,
1009 raw.ptr,
1010 ))?;
1011 }
1012 x => return Err(MinionError::NotImplemented(format!("{x:?}"))),
1013 };
1014
1015 let var_result = ffi::minion_getVarByName(instance, c_str.as_ptr() as _);
1016 check_minion_result(var_result.result)?;
1017 let var = var_result.var;
1018
1019 ffi::printMatrix_addVar(instance, var);
1020
1021 print_vars.push(var_name.clone());
1023 }
1024
1025 let primary_search_var_names: std::collections::HashSet<VarName> = model
1027 .named_variables
1028 .get_search_variable_order()
1029 .into_iter()
1030 .collect();
1031 for search_var_name in model.named_variables.get_search_variable_order() {
1032 let c_str = CString::new(search_var_name.clone()).map_err(|_| {
1033 anyhow!(
1034 "Variable name {:?} contains a null character.",
1035 search_var_name.clone()
1036 )
1037 })?;
1038 let var_result = ffi::minion_getVarByName(instance, c_str.as_ptr() as _);
1039 check_minion_result(var_result.result)?;
1040 ffi::vec_var_push_back(search_vars.ptr, var_result.var);
1041 }
1042
1043 let search_order = Scoped::new(
1044 ffi::searchOrder_new(search_vars.ptr, options.var_order.to_ffi(), false),
1045 |x| ffi::searchOrder_free(x as _),
1046 );
1047 ffi::searchOrder_setValOrder(search_order.ptr, options.val_order.to_ffi());
1052
1053 ffi::instance_addSearchOrder(instance, search_order.ptr);
1054
1055 let auxiliary_search_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
1063 let mut has_auxiliary_search_vars = false;
1064 for auxiliary_name in model
1065 .named_variables
1066 .get_variable_order()
1067 .into_iter()
1068 .filter(|name| !primary_search_var_names.contains(name))
1069 {
1070 let c_str = CString::new(auxiliary_name.clone())
1071 .map_err(|_| anyhow!("Variable name {auxiliary_name:?} contains a null character"))?;
1072 let var_result = ffi::minion_getVarByName(instance, c_str.as_ptr() as _);
1073 check_minion_result(var_result.result)?;
1074 ffi::vec_var_push_back(auxiliary_search_vars.ptr, var_result.var);
1075 has_auxiliary_search_vars = true;
1076 }
1077 if has_auxiliary_search_vars {
1078 let auxiliary_search_order = Scoped::new(
1079 ffi::searchOrder_new(
1080 auxiliary_search_vars.ptr,
1081 ffi::VarOrderEnum_ORDER_STATIC,
1082 true,
1083 ),
1084 |x| ffi::searchOrder_free(x as _),
1085 );
1086 ffi::instance_addSearchOrder(instance, auxiliary_search_order.ptr);
1087 }
1088
1089 for (name, tuples) in &model.tuple_tables {
1098 let c_name = CString::new(name.clone()).map_err(|_| {
1099 anyhow!(
1100 "Tuple-table name {:?} contains a null character.",
1101 name.clone()
1102 )
1103 })?;
1104
1105 let raw_tuples = Scoped::new(ffi::vec_vec_int_new(), |x| ffi::vec_vec_int_free(x as _));
1110 for tuple in tuples {
1111 let raw_tuple = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
1112 for constant in tuple {
1113 let val = match constant {
1114 Constant::Integer(n) => *n,
1115 Constant::Bool(true) => 1,
1116 Constant::Bool(false) => 0,
1117 #[allow(unreachable_patterns)]
1118 x => return Err(MinionError::NotImplemented(format!("{x:?}"))),
1119 };
1120 ffi::vec_int_push_back(raw_tuple.ptr, val);
1121 }
1122 ffi::vec_vec_int_push_back_ptr(raw_tuples.ptr, raw_tuple.ptr);
1123 }
1124
1125 let raw_tuple_list = ffi::tupleList_new(raw_tuples.ptr);
1129 ffi::instance_addTupleTableSymbol(instance, c_name.as_ptr() as *mut c_char, raw_tuple_list);
1130 }
1131
1132 for constraint in &model.constraints {
1137 let constraint_type = get_constraint_type(constraint)?;
1142 let raw_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
1143 ffi::constraint_free(x as _)
1144 });
1145
1146 constraint_add_args(instance, raw_constraint.ptr, constraint)?;
1147 ffi::instance_addConstraint(instance, raw_constraint.ptr);
1148 }
1149
1150 if let Some(opt) = &model.optimise {
1154 let mut raw_var = resolve_var(instance, &opt.var)?;
1155 ffi::instance_setOptimise(instance, opt.minimise, &mut raw_var);
1156 }
1157
1158 Ok(())
1159}
1160
1161unsafe fn get_constraint_type(constraint: &Constraint) -> Result<u32, MinionError> {
1162 match constraint {
1163 Constraint::SumGeq(_, _) => Ok(ffi::ConstraintType_CT_GEQSUM),
1164 Constraint::SumLeq(_, _) => Ok(ffi::ConstraintType_CT_LEQSUM),
1165 Constraint::Ineq(_, _, _) => Ok(ffi::ConstraintType_CT_INEQ),
1166 Constraint::Eq(_, _) => Ok(ffi::ConstraintType_CT_EQ),
1167 Constraint::Difference(_, _) => Ok(ffi::ConstraintType_CT_DIFFERENCE),
1168 Constraint::Div(_, _) => Ok(ffi::ConstraintType_CT_DIV),
1169 Constraint::DivUndefZero(_, _) => Ok(ffi::ConstraintType_CT_DIV_UNDEFZERO),
1170 Constraint::Modulo(_, _) => Ok(ffi::ConstraintType_CT_MODULO),
1171 Constraint::ModuloUndefZero(_, _) => Ok(ffi::ConstraintType_CT_MODULO_UNDEFZERO),
1172 Constraint::Pow(_, _) => Ok(ffi::ConstraintType_CT_POW),
1173 Constraint::Product(_, _) => Ok(ffi::ConstraintType_CT_PRODUCT2),
1174 Constraint::WeightedSumGeq(_, _, _) => Ok(ffi::ConstraintType_CT_WEIGHTGEQSUM),
1175 Constraint::WeightedSumLeq(_, _, _) => Ok(ffi::ConstraintType_CT_WEIGHTLEQSUM),
1176 Constraint::CheckAssign(_) => Ok(ffi::ConstraintType_CT_CHECK_ASSIGN),
1177 Constraint::CheckGsa(_) => Ok(ffi::ConstraintType_CT_CHECK_GSA),
1178 Constraint::ForwardChecking(_) => Ok(ffi::ConstraintType_CT_FORWARD_CHECKING),
1179 Constraint::Reify(_, _) => Ok(ffi::ConstraintType_CT_REIFY),
1180 Constraint::ReifyImply(_, _) => Ok(ffi::ConstraintType_CT_REIFYIMPLY),
1181 Constraint::ReifyImplyQuick(_, _) => Ok(ffi::ConstraintType_CT_REIFYIMPLY_QUICK),
1182 Constraint::WatchedAnd(_) => Ok(ffi::ConstraintType_CT_WATCHED_NEW_AND),
1183 Constraint::WatchedOr(_) => Ok(ffi::ConstraintType_CT_WATCHED_NEW_OR),
1184 Constraint::GacAllDiff(_) => Ok(ffi::ConstraintType_CT_GACALLDIFF),
1185 Constraint::AllDiff(_) => Ok(ffi::ConstraintType_CT_ALLDIFF),
1186 Constraint::AllDiffMatrix(_, _) => Ok(ffi::ConstraintType_CT_ALLDIFFMATRIX),
1187 Constraint::WatchSumGeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_GEQSUM),
1188 Constraint::WatchSumLeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LEQSUM),
1189 Constraint::OccurrenceGeq(_, _, _) => Ok(ffi::ConstraintType_CT_GEQ_OCCURRENCE),
1190 Constraint::OccurrenceLeq(_, _, _) => Ok(ffi::ConstraintType_CT_LEQ_OCCURRENCE),
1191 Constraint::Occurrence(_, _, _) => Ok(ffi::ConstraintType_CT_OCCURRENCE),
1192 Constraint::LitSumGeq(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_LITSUM),
1193 Constraint::Gcc(_, _, _) => Ok(ffi::ConstraintType_CT_GCC),
1194 Constraint::GccWeak(_, _, _) => Ok(ffi::ConstraintType_CT_GCCWEAK),
1195 Constraint::LexLeqRv(_, _) => Ok(ffi::ConstraintType_CT_GACLEXLEQ),
1196 Constraint::LexLeq(_, _) => Ok(ffi::ConstraintType_CT_LEXLEQ),
1197 Constraint::LexLess(_, _) => Ok(ffi::ConstraintType_CT_LEXLESS),
1198 Constraint::LexLeqQuick(_, _) => Ok(ffi::ConstraintType_CT_QUICK_LEXLEQ),
1199 Constraint::LexLessQuick(_, _) => Ok(ffi::ConstraintType_CT_QUICK_LEXLESS),
1200 Constraint::WatchVecNeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_VECNEQ),
1201 Constraint::WatchVecExistsLess(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_VEC_OR_LESS),
1202 Constraint::Hamming(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_HAMMING),
1203 Constraint::NotHamming(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_HAMMING),
1204 Constraint::FrameUpdate(_, _, _, _, _) => Ok(ffi::ConstraintType_CT_FRAMEUPDATE),
1205 Constraint::NegativeTable(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NEGATIVE_TABLE),
1206 Constraint::Table(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_TABLE),
1207 Constraint::GacSchema(_, _) => Ok(ffi::ConstraintType_CT_GACSCHEMA),
1208 Constraint::LightTable(_, _) => Ok(ffi::ConstraintType_CT_LIGHTTABLE),
1209 Constraint::Mddc(_, _) => Ok(ffi::ConstraintType_CT_MDDC),
1210 Constraint::NegativeMddc(_, _) => Ok(ffi::ConstraintType_CT_NEGATIVEMDDC),
1211 Constraint::Str2Plus(_, _) => Ok(ffi::ConstraintType_CT_STR),
1212 Constraint::ShortStr2(_, _) => Ok(ffi::ConstraintType_CT_SHORTSTR),
1213 Constraint::HaggisGac(_, _) => Ok(ffi::ConstraintType_CT_HAGGISGAC),
1214 Constraint::HaggisGacStable(_, _) => Ok(ffi::ConstraintType_CT_HAGGISGAC_STABLE),
1215 Constraint::ShortCTupleStr2(_, _) => Ok(ffi::ConstraintType_CT_SHORTSTR_CTUPLE),
1216 Constraint::Max(_, _) => Ok(ffi::ConstraintType_CT_MAX),
1217 Constraint::Min(_, _) => Ok(ffi::ConstraintType_CT_MIN),
1218 Constraint::NvalueGeq(_, _) => Ok(ffi::ConstraintType_CT_GEQNVALUE),
1219 Constraint::NvalueLeq(_, _) => Ok(ffi::ConstraintType_CT_LEQNVALUE),
1220 Constraint::Element(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT),
1221 Constraint::ElementOne(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT_ONE),
1222 Constraint::ElementUndefZero(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT_UNDEFZERO),
1223 Constraint::WatchElement(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT),
1224 Constraint::WatchElementOne(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_ONE),
1225 Constraint::WatchElementOneUndefZero(_, _, _) => {
1226 Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_ONE_UNDEFZERO)
1227 }
1228 Constraint::WatchElementUndefZero(_, _, _) => {
1229 Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_UNDEFZERO)
1230 }
1231 Constraint::WLiteral(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LIT),
1232 Constraint::WNotLiteral(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOTLIT),
1233 Constraint::WInIntervalSet(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_ININTERVALSET),
1234 Constraint::WInRange(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_INRANGE),
1235 Constraint::WInset(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_INSET),
1236 Constraint::WNotInRange(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_INRANGE),
1237 Constraint::WNotInset(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_INSET),
1238 Constraint::Abs(_, _) => Ok(ffi::ConstraintType_CT_ABS),
1239 Constraint::DisEq(_, _) => Ok(ffi::ConstraintType_CT_DISEQ),
1240 Constraint::MinusEq(_, _) => Ok(ffi::ConstraintType_CT_MINUSEQ),
1241 Constraint::GacEq(_, _) => Ok(ffi::ConstraintType_CT_GACEQ),
1242 Constraint::WatchLess(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LESS),
1243 Constraint::WatchNeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NEQ),
1244 Constraint::True => Ok(ffi::ConstraintType_CT_TRUE),
1245 Constraint::False => Ok(ffi::ConstraintType_CT_FALSE),
1246
1247 #[allow(unreachable_patterns)]
1248 x => Err(MinionError::NotImplemented(format!(
1249 "Constraint not implemented {x:?}",
1250 ))),
1251 }
1252}
1253
1254unsafe fn constraint_add_args(
1255 i: *mut ffi::ProbSpec_CSPInstance,
1256 r_constr: *mut ffi::ProbSpec_ConstraintBlob,
1257 constr: &Constraint,
1258) -> Result<(), MinionError> {
1259 match constr {
1260 Constraint::SumGeq(lhs_vars, rhs_var) => {
1261 read_list(i, r_constr, lhs_vars)?;
1262 read_var(i, r_constr, rhs_var)?;
1263 Ok(())
1264 }
1265 Constraint::SumLeq(lhs_vars, rhs_var) => {
1266 read_list(i, r_constr, lhs_vars)?;
1267 read_var(i, r_constr, rhs_var)?;
1268 Ok(())
1269 }
1270 Constraint::Ineq(var1, var2, c) => {
1271 read_var(i, r_constr, var1)?;
1272 read_var(i, r_constr, var2)?;
1273 read_constant(r_constr, c)?;
1274 Ok(())
1275 }
1276 Constraint::Eq(var1, var2) => {
1277 read_var(i, r_constr, var1)?;
1278 read_var(i, r_constr, var2)?;
1279 Ok(())
1280 }
1281 Constraint::Difference((a, b), c) => {
1282 read_2_vars(i, r_constr, a, b)?;
1283 read_var(i, r_constr, c)?;
1284 Ok(())
1285 }
1286 Constraint::Div((a, b), c) => {
1287 read_2_vars(i, r_constr, a, b)?;
1288 read_var(i, r_constr, c)?;
1289 Ok(())
1290 }
1291 Constraint::DivUndefZero((a, b), c) => {
1292 read_2_vars(i, r_constr, a, b)?;
1293 read_var(i, r_constr, c)?;
1294 Ok(())
1295 }
1296 Constraint::Modulo((a, b), c) => {
1297 read_2_vars(i, r_constr, a, b)?;
1298 read_var(i, r_constr, c)?;
1299 Ok(())
1300 }
1301 Constraint::ModuloUndefZero((a, b), c) => {
1302 read_2_vars(i, r_constr, a, b)?;
1303 read_var(i, r_constr, c)?;
1304 Ok(())
1305 }
1306 Constraint::Pow((a, b), c) => {
1307 read_2_vars(i, r_constr, a, b)?;
1308 read_var(i, r_constr, c)?;
1309 Ok(())
1310 }
1311 Constraint::Product((a, b), c) => {
1312 read_2_vars(i, r_constr, a, b)?;
1313 read_var(i, r_constr, c)?;
1314 Ok(())
1315 }
1316 Constraint::WeightedSumGeq(a, b, c) => {
1317 read_constant_list(r_constr, a)?;
1318 read_list(i, r_constr, b)?;
1319 read_var(i, r_constr, c)?;
1320 Ok(())
1321 }
1322 Constraint::WeightedSumLeq(a, b, c) => {
1323 read_constant_list(r_constr, a)?;
1324 read_list(i, r_constr, b)?;
1325 read_var(i, r_constr, c)?;
1326 Ok(())
1327 }
1328 Constraint::CheckAssign(a) => {
1329 read_constraint(i, r_constr, (**a).clone())?;
1330 Ok(())
1331 }
1332 Constraint::CheckGsa(a) => {
1333 read_constraint(i, r_constr, (**a).clone())?;
1334 Ok(())
1335 }
1336 Constraint::ForwardChecking(a) => {
1337 read_constraint(i, r_constr, (**a).clone())?;
1338 Ok(())
1339 }
1340 Constraint::Reify(a, b) => {
1341 read_constraint(i, r_constr, (**a).clone())?;
1342 read_var(i, r_constr, b)?;
1343 Ok(())
1344 }
1345 Constraint::ReifyImply(a, b) => {
1346 read_constraint(i, r_constr, (**a).clone())?;
1347 read_var(i, r_constr, b)?;
1348 Ok(())
1349 }
1350 Constraint::ReifyImplyQuick(a, b) => {
1351 read_constraint(i, r_constr, (**a).clone())?;
1352 read_var(i, r_constr, b)?;
1353 Ok(())
1354 }
1355 Constraint::WatchedAnd(a) => {
1356 read_constraint_list(i, r_constr, a)?;
1357 Ok(())
1358 }
1359 Constraint::WatchedOr(a) => {
1360 read_constraint_list(i, r_constr, a)?;
1361 Ok(())
1362 }
1363 Constraint::GacAllDiff(a) => {
1364 read_list(i, r_constr, a)?;
1365 Ok(())
1366 }
1367 Constraint::AllDiff(a) => {
1368 read_list(i, r_constr, a)?;
1369 Ok(())
1370 }
1371 Constraint::AllDiffMatrix(a, b) => {
1372 read_list(i, r_constr, a)?;
1373 read_constant(r_constr, b)?;
1374 Ok(())
1375 }
1376 Constraint::WatchSumGeq(a, b) => {
1377 read_list(i, r_constr, a)?;
1378 read_constant(r_constr, b)?;
1379 Ok(())
1380 }
1381 Constraint::WatchSumLeq(a, b) => {
1382 read_list(i, r_constr, a)?;
1383 read_constant(r_constr, b)?;
1384 Ok(())
1385 }
1386 Constraint::OccurrenceGeq(a, b, c) => {
1387 read_list(i, r_constr, a)?;
1388 read_constant(r_constr, b)?;
1389 read_constant(r_constr, c)?;
1390 Ok(())
1391 }
1392 Constraint::OccurrenceLeq(a, b, c) => {
1393 read_list(i, r_constr, a)?;
1394 read_constant(r_constr, b)?;
1395 read_constant(r_constr, c)?;
1396 Ok(())
1397 }
1398 Constraint::Occurrence(a, b, c) => {
1399 read_list(i, r_constr, a)?;
1400 read_constant(r_constr, b)?;
1401 read_var(i, r_constr, c)?;
1402 Ok(())
1403 }
1404 Constraint::LexLess(a, b)
1405 | Constraint::LexLeq(a, b)
1406 | Constraint::LexLeqRv(a, b)
1407 | Constraint::LexLeqQuick(a, b)
1408 | Constraint::LexLessQuick(a, b)
1409 | Constraint::WatchVecNeq(a, b)
1410 | Constraint::WatchVecExistsLess(a, b) => {
1411 read_list(i, r_constr, a)?;
1412 read_list(i, r_constr, b)?;
1413 Ok(())
1414 }
1415 Constraint::LitSumGeq(a, b, c) => {
1416 read_list(i, r_constr, a)?;
1417 read_constant_list(r_constr, b)?;
1418 read_constant(r_constr, c)?;
1419 Ok(())
1420 }
1421 Constraint::Gcc(a, b, c) | Constraint::GccWeak(a, b, c) => {
1422 read_list(i, r_constr, a)?;
1423 read_constant_list(r_constr, b)?;
1424 read_list(i, r_constr, c)?;
1425 Ok(())
1426 }
1427 Constraint::Hamming(a, b, c) | Constraint::NotHamming(a, b, c) => {
1428 read_list(i, r_constr, a)?;
1429 read_list(i, r_constr, b)?;
1430 read_constant(r_constr, c)?;
1431 Ok(())
1432 }
1433 Constraint::FrameUpdate(a, b, c, d, e) => {
1434 read_list(i, r_constr, a)?;
1435 read_list(i, r_constr, b)?;
1436 read_list(i, r_constr, c)?;
1437 read_list(i, r_constr, d)?;
1438 read_constant(r_constr, e)?;
1439 Ok(())
1440 }
1441 Constraint::NegativeTable(vars, tuple_list)
1442 | Constraint::Table(vars, tuple_list)
1443 | Constraint::GacSchema(vars, tuple_list)
1444 | Constraint::LightTable(vars, tuple_list)
1445 | Constraint::Mddc(vars, tuple_list)
1446 | Constraint::NegativeMddc(vars, tuple_list) => {
1447 read_list(i, r_constr, vars)?;
1448 read_tuple_list(r_constr, tuple_list)?;
1449 Ok(())
1450 }
1451 Constraint::Str2Plus(vars, table_var) => {
1452 read_list(i, r_constr, vars)?;
1459 let name = match table_var {
1460 Var::NameRef(n) => n.clone(),
1461 other => {
1462 return Err(MinionError::NotImplemented(format!(
1463 "Str2Plus second argument must be Var::NameRef(tuple_table_name), got {other:?}"
1464 )));
1465 }
1466 };
1467 let c_name = CString::new(name.clone())
1468 .map_err(|_| anyhow!("Tuple-table name {name:?} contains a null character."))?;
1469 ffi::constraint_setTuplesByName(r_constr, i, c_name.as_ptr());
1470 Ok(())
1471 }
1472 Constraint::ShortStr2(vars, short_tuples)
1473 | Constraint::HaggisGac(vars, short_tuples)
1474 | Constraint::HaggisGacStable(vars, short_tuples)
1475 | Constraint::ShortCTupleStr2(vars, short_tuples) => {
1476 read_list(i, r_constr, vars)?;
1477 read_short_tuple_list(r_constr, short_tuples)?;
1478 Ok(())
1479 }
1480 Constraint::Max(a, b)
1481 | Constraint::Min(a, b)
1482 | Constraint::NvalueGeq(a, b)
1483 | Constraint::NvalueLeq(a, b) => {
1484 read_list(i, r_constr, a)?;
1485 read_var(i, r_constr, b)?;
1486 Ok(())
1487 }
1488 Constraint::Element(vec, j, e)
1489 | Constraint::ElementOne(vec, j, e)
1490 | Constraint::ElementUndefZero(vec, j, e)
1491 | Constraint::WatchElement(vec, j, e)
1492 | Constraint::WatchElementOne(vec, j, e)
1493 | Constraint::WatchElementOneUndefZero(vec, j, e)
1494 | Constraint::WatchElementUndefZero(vec, j, e) => {
1495 read_list(i, r_constr, vec)?;
1496 read_var(i, r_constr, j)?;
1497 read_var(i, r_constr, e)?;
1498 Ok(())
1499 }
1500 Constraint::WLiteral(a, b) | Constraint::WNotLiteral(a, b) => {
1501 read_var(i, r_constr, a)?;
1502 read_constant(r_constr, b)?;
1503 Ok(())
1504 }
1505 Constraint::WInIntervalSet(var, consts)
1506 | Constraint::WInRange(var, consts)
1507 | Constraint::WNotInRange(var, consts)
1508 | Constraint::WInset(var, consts)
1509 | Constraint::WNotInset(var, consts) => {
1510 read_var(i, r_constr, var)?;
1511 read_constant_list(r_constr, consts)?;
1512 Ok(())
1513 }
1514 Constraint::Abs(a, b)
1515 | Constraint::DisEq(a, b)
1516 | Constraint::MinusEq(a, b)
1517 | Constraint::GacEq(a, b)
1518 | Constraint::WatchLess(a, b)
1519 | Constraint::WatchNeq(a, b) => {
1520 read_var(i, r_constr, a)?;
1521 read_var(i, r_constr, b)?;
1522 Ok(())
1523 }
1524 Constraint::AllDiffMatrix(vars, c) => {
1525 read_list(i, r_constr, vars)?;
1526 read_constant(r_constr, c)?;
1527 Ok(())
1528 }
1529
1530 Constraint::True => Ok(()),
1531 Constraint::False => Ok(()),
1532 #[allow(unreachable_patterns)]
1533 x => Err(MinionError::NotImplemented(format!("{x:?}"))),
1534 }
1535}
1536
1537unsafe fn resolve_var(
1541 instance: *mut ffi::ProbSpec_CSPInstance,
1542 var: &Var,
1543) -> Result<ffi::ProbSpec_Var, MinionError> {
1544 match var {
1545 Var::NameRef(name) => {
1546 let c_str = CString::new(name.clone()).map_err(|_| {
1547 anyhow!(
1548 "Variable name {:?} contains a null character.",
1549 name.clone()
1550 )
1551 })?;
1552 let var_result = ffi::minion_getVarByName(instance, c_str.as_ptr() as _);
1553 check_minion_result(var_result.result)?;
1554 Ok(var_result.var)
1555 }
1556 Var::ConstantAsVar(n) => Ok(ffi::constantAsVar(*n)),
1557 }
1558}
1559
1560unsafe fn read_list(
1561 instance: *mut ffi::ProbSpec_CSPInstance,
1562 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1563 vars: &Vec<Var>,
1564) -> Result<(), MinionError> {
1565 let raw_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
1566 for var in vars {
1567 let raw_var = resolve_var(instance, var)?;
1568 ffi::vec_var_push_back(raw_vars.ptr, raw_var);
1569 }
1570
1571 ffi::constraint_addList(raw_constraint, raw_vars.ptr);
1572
1573 Ok(())
1574}
1575
1576unsafe fn read_var(
1577 instance: *mut ffi::ProbSpec_CSPInstance,
1578 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1579 var: &Var,
1580) -> Result<(), MinionError> {
1581 let raw_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
1582 let raw_var = resolve_var(instance, var)?;
1583 ffi::vec_var_push_back(raw_vars.ptr, raw_var);
1584 ffi::constraint_addList(raw_constraint, raw_vars.ptr);
1585
1586 Ok(())
1587}
1588
1589unsafe fn read_2_vars(
1590 instance: *mut ffi::ProbSpec_CSPInstance,
1591 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1592 var1: &Var,
1593 var2: &Var,
1594) -> Result<(), MinionError> {
1595 let mut raw_var = resolve_var(instance, var1)?;
1596 let mut raw_var2 = resolve_var(instance, var2)?;
1597 ffi::constraint_addTwoVars(raw_constraint, &mut raw_var, &mut raw_var2);
1601 Ok(())
1602}
1603
1604unsafe fn read_constant(
1605 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1606 constant: &Constant,
1607) -> Result<(), MinionError> {
1608 let val: i32 = match constant {
1609 Constant::Integer(n) => Ok(*n),
1610 Constant::Bool(true) => Ok(1),
1611 Constant::Bool(false) => Ok(0),
1612 x => Err(MinionError::NotImplemented(format!("{x:?}"))),
1613 }?;
1614
1615 ffi::constraint_addConstant(raw_constraint, val);
1616
1617 Ok(())
1618}
1619
1620unsafe fn read_constant_list(
1621 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1622 constants: &[Constant],
1623) -> Result<(), MinionError> {
1624 let raw_consts = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
1625
1626 for constant in constants.iter() {
1627 let val = match constant {
1628 Constant::Integer(n) => Ok(*n),
1629 Constant::Bool(true) => Ok(1),
1630 Constant::Bool(false) => Ok(0),
1631 #[allow(unreachable_patterns)] x => Err(MinionError::NotImplemented(format!("{x:?}"))),
1633 }?;
1634
1635 ffi::vec_int_push_back(raw_consts.ptr, val);
1636 }
1637
1638 ffi::constraint_addConstantList(raw_constraint, raw_consts.ptr);
1639 Ok(())
1640}
1641
1642unsafe fn read_constraint(
1646 instance: *mut ffi::ProbSpec_CSPInstance,
1647 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1648 inner_constraint: Constraint,
1649) -> Result<(), MinionError> {
1650 let constraint_type = get_constraint_type(&inner_constraint)?;
1651 let raw_inner_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
1652 ffi::constraint_free(x as _)
1653 });
1654
1655 constraint_add_args(instance, raw_inner_constraint.ptr, &inner_constraint)?;
1656
1657 ffi::constraint_addConstraint(raw_constraint, raw_inner_constraint.ptr);
1658 Ok(())
1659}
1660
1661unsafe fn read_constraint_list(
1662 instance: *mut ffi::ProbSpec_CSPInstance,
1663 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1664 inner_constraints: &[Constraint],
1665) -> Result<(), MinionError> {
1666 let raw_inners = Scoped::new(ffi::vec_constraints_new(), |x| {
1667 ffi::vec_constraints_free(x as _)
1668 });
1669 for inner_constraint in inner_constraints.iter() {
1670 let constraint_type = get_constraint_type(inner_constraint)?;
1671 let raw_inner_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
1672 ffi::constraint_free(x as _)
1673 });
1674
1675 constraint_add_args(instance, raw_inner_constraint.ptr, inner_constraint)?;
1676 ffi::vec_constraints_push_back(raw_inners.ptr, raw_inner_constraint.ptr);
1677 }
1678
1679 ffi::constraint_addConstraintList(raw_constraint, raw_inners.ptr);
1680 Ok(())
1681}
1682
1683unsafe fn read_tuple_list(
1684 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1685 tuples: &Vec<Tuple>,
1686) -> Result<(), MinionError> {
1687 let raw_tuples = Scoped::new(ffi::vec_vec_int_new(), |x| ffi::vec_vec_int_free(x as _));
1689 for tuple in tuples {
1690 let raw_tuple = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
1691 for constant in tuple.iter() {
1692 let val = match constant {
1693 Constant::Integer(n) => Ok(*n),
1694 Constant::Bool(true) => Ok(1),
1695 Constant::Bool(false) => Ok(0),
1696 #[allow(unreachable_patterns)] x => Err(MinionError::NotImplemented(format!("{:?}", x))),
1698 }?;
1699
1700 ffi::vec_int_push_back(raw_tuple.ptr, val);
1701 }
1702
1703 ffi::vec_vec_int_push_back_ptr(raw_tuples.ptr, raw_tuple.ptr);
1704 }
1705
1706 let raw_tuple_list = ffi::tupleList_new(raw_tuples.ptr);
1709 ffi::constraint_setTuples(raw_constraint, raw_tuple_list);
1710
1711 Ok(())
1712}
1713
1714unsafe fn read_short_tuple_list(
1720 raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
1721 short_tuples: &Vec<ShortTuple>,
1722) -> Result<(), MinionError> {
1723 let raw_outer = Scoped::new(ffi::vec_vec_int_new(), |x| ffi::vec_vec_int_free(x as _));
1724 for short in short_tuples {
1725 let raw_inner = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
1726 for &(idx, ref constant) in short {
1727 let val = match constant {
1728 Constant::Integer(n) => Ok(*n),
1729 Constant::Bool(true) => Ok(1),
1730 Constant::Bool(false) => Ok(0),
1731 #[allow(unreachable_patterns)]
1732 x => Err(MinionError::NotImplemented(format!("{:?}", x))),
1733 }?;
1734 ffi::vec_int_push_back(raw_inner.ptr, idx as i32);
1735 ffi::vec_int_push_back(raw_inner.ptr, val);
1736 }
1737 ffi::vec_vec_int_push_back_ptr(raw_outer.ptr, raw_inner.ptr);
1738 }
1739
1740 let raw_short_list = ffi::shortTupleList_new(raw_outer.ptr);
1743 ffi::constraint_setShortTuples(raw_constraint, raw_short_list);
1744
1745 Ok(())
1746}