1use std::{
2 cell::{Cell, RefCell},
3 collections::VecDeque,
4 fmt::Display,
5 io::{self, BufRead, Write},
6 str::FromStr,
7};
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use strum_macros::{Display as StrumDisplay, EnumIter};
12
13use crate::bug;
14use crate::bug_assert;
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18pub enum Heuristic {
19 First,
20 Random,
21 #[default]
22 Compact,
23 Interactive,
25 All,
28}
29
30impl Display for Heuristic {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.write_str(match self {
33 Self::First => "f",
34 Self::Random => "r",
35 Self::Compact => "c",
36 Self::Interactive => "i",
37 Self::All => "x",
38 })
39 }
40}
41
42impl FromStr for Heuristic {
43 type Err = String;
44
45 fn from_str(value: &str) -> Result<Self, Self::Err> {
46 match value.trim().to_ascii_lowercase().as_str() {
47 "f" | "first" => Ok(Self::First),
48 "r" | "random" => Ok(Self::Random),
49 "c" | "compact" => Ok(Self::Compact),
50 "i" | "interactive" => Ok(Self::Interactive),
51 "x" | "all" => Ok(Self::All),
52 other => Err(format!(
53 "unknown heuristic '{other}'; expected one of: f, r, c, i, x"
54 )),
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
61pub enum Channelling {
62 #[default]
63 No,
64 Yes,
65 Uniform,
67}
68
69impl Display for Channelling {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.write_str(match self {
72 Self::No => "no",
73 Self::Yes => "yes",
74 Self::Uniform => "uniform",
75 })
76 }
77}
78
79impl FromStr for Channelling {
80 type Err = String;
81
82 fn from_str(value: &str) -> Result<Self, Self::Err> {
83 match value.trim().to_ascii_lowercase().as_str() {
84 "no" => Ok(Self::No),
85 "yes" => Ok(Self::Yes),
86 "uniform" => Ok(Self::Uniform),
87 other => Err(format!(
88 "unknown channelling setting '{other}'; expected yes, no, or uniform"
89 )),
90 }
91 }
92}
93
94pub const DEFAULT_HEURISTIC_SEED: u64 = 0;
95
96thread_local! {
97 static ELEMENT_ENCODING: Cell<Option<crate::ast::sat_decision::ElementEncoding>> = const { Cell::new(None) };
98 static TABLE_ENCODING: Cell<Option<crate::ast::sat_decision::TableEncoding>> = const { Cell::new(None) };
99 static ALLDIFFERENT_ENCODING: Cell<Option<crate::ast::sat_decision::AllDifferentEncoding>> = const { Cell::new(None) };
100 static PB_ENCODING: Cell<Option<crate::ast::sat_decision::PbEncoding>> = const { Cell::new(None) };
101 static CARDINALITY_ENCODING: Cell<Option<crate::ast::sat_decision::CardinalityEncoding>> = const { Cell::new(None) };
102 static AMO_ENCODING: Cell<Option<crate::ast::sat_decision::AmoEncoding>> = const { Cell::new(None) };
103 static HEURISTIC: Cell<Heuristic> = const { Cell::new(Heuristic::Compact) };
104 static CHANNELLING: Cell<Channelling> = const { Cell::new(Channelling::No) };
105 static HEURISTIC_RANDOM_STATE: Cell<u64> = const { Cell::new(DEFAULT_HEURISTIC_SEED) };
106 static HEURISTIC_ALL_CHOICES: RefCell<AllChoicesState> =
107 const { RefCell::new(AllChoicesState::new()) };
108 static HEURISTIC_RESPONSES: RefCell<VecDeque<usize>> = const { RefCell::new(VecDeque::new()) };
110}
111
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct HeuristicChoice {
114 pub selected: usize,
115 pub options: Vec<String>,
116}
117
118#[derive(Debug)]
119struct AllChoicesState {
120 requested: Vec<usize>,
121 decisions: Vec<HeuristicChoice>,
122}
123
124impl AllChoicesState {
125 const fn new() -> Self {
126 Self {
127 requested: Vec::new(),
128 decisions: Vec::new(),
129 }
130 }
131}
132
133pub fn set_heuristic(heuristic: Heuristic) {
134 HEURISTIC.with(|current| current.set(heuristic));
135}
136
137pub fn heuristic() -> Heuristic {
138 HEURISTIC.with(Cell::get)
139}
140
141pub fn set_channelling(channelling: Channelling) {
142 CHANNELLING.with(|current| current.set(channelling));
143 crate::representation::uniform::reset();
144}
145
146pub fn channelling() -> Channelling {
147 CHANNELLING.with(Cell::get)
148}
149
150pub fn set_heuristic_seed(seed: u64) {
151 HEURISTIC_RANDOM_STATE.with(|state| state.set(seed));
152}
153
154pub fn set_heuristic_responses(responses: Vec<usize>) {
159 HEURISTIC_RESPONSES.with(|state| {
160 *state.borrow_mut() = VecDeque::from(responses);
161 });
162}
163
164pub fn clear_heuristic_responses() {
166 HEURISTIC_RESPONSES.with(|state| state.borrow_mut().clear());
167}
168
169pub fn next_heuristic_random_index(upper_bound: usize) -> usize {
171 bug_assert!(
172 upper_bound > 0,
173 "random choice requires at least one candidate"
174 );
175 HEURISTIC_RANDOM_STATE.with(|state| {
176 let next = state.get().wrapping_add(0x9e37_79b9_7f4a_7c15);
178 state.set(next);
179 let mut mixed = next;
180 mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
181 mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
182 mixed ^= mixed >> 31;
183 (mixed as usize) % upper_bound
184 })
185}
186
187pub fn begin_heuristic_all_choices(requested: Vec<usize>) {
189 HEURISTIC_ALL_CHOICES.with(|state| {
190 *state.borrow_mut() = AllChoicesState {
191 requested,
192 decisions: Vec::new(),
193 };
194 });
195}
196
197struct HeuristicGuard(Heuristic);
199
200impl Drop for HeuristicGuard {
201 fn drop(&mut self) {
202 set_heuristic(self.0);
203 }
204}
205
206pub fn with_compact_heuristic<T>(f: impl FnOnce() -> T) -> T {
218 let previous = heuristic();
219 set_heuristic(Heuristic::Compact);
220 let _guard = HeuristicGuard(previous);
221 f()
222}
223
224pub fn next_heuristic_all_index(options: &[&str]) -> usize {
226 bug_assert!(
227 !options.is_empty(),
228 "all-choice requires at least one candidate"
229 );
230 HEURISTIC_ALL_CHOICES.with(|state| {
231 let mut state = state.borrow_mut();
232 let decision_index = state.decisions.len();
233 let selected = state.requested.get(decision_index).copied().unwrap_or(0);
234 assert!(
235 selected < options.len(),
236 "requested heuristic branch {selected} but only {} options exist",
237 options.len()
238 );
239 state.decisions.push(HeuristicChoice {
240 selected,
241 options: options.iter().map(|option| (*option).to_string()).collect(),
242 });
243 selected
244 })
245}
246
247pub fn heuristic_all_choices() -> Vec<HeuristicChoice> {
248 HEURISTIC_ALL_CHOICES.with(|state| state.borrow().decisions.clone())
249}
250
251pub fn next_heuristic_interactive_index(options: &[&str]) -> usize {
256 bug_assert!(
257 !options.is_empty(),
258 "interactive choice requires at least one candidate"
259 );
260
261 for (index, option) in options.iter().enumerate() {
262 eprintln!("{}. {}", index + 1, option);
263 }
264
265 let recorded = HEURISTIC_RESPONSES.with(|state| state.borrow_mut().pop_front());
266 let one_based = match recorded {
267 Some(recorded) => {
268 eprintln!("Response: {recorded}");
269 assert!(
270 recorded >= 1 && recorded <= options.len(),
271 "recorded response {recorded} out of range; expected a value between 1 and {}",
272 options.len()
273 );
274 recorded
275 }
276 None => prompt_heuristic_interactive_choice(options.len()),
277 };
278 one_based - 1
279}
280
281fn prompt_heuristic_interactive_choice(option_count: usize) -> usize {
282 let stdin = io::stdin();
283 let mut stdout = io::stderr();
284 loop {
285 eprint!("Pick option: ");
286 let _ = stdout.flush();
287 let mut line = String::new();
288 if stdin.lock().read_line(&mut line).is_err() {
289 eprintln!("Failed to read interactive heuristic response; defaulting to 1");
290 return 1;
291 }
292 let trimmed = line.trim();
293 if trimmed.is_empty() {
294 return 1;
295 }
296 match trimmed.parse::<usize>() {
297 Ok(value) if value >= 1 && value <= option_count => return value,
298 Ok(_) => {
299 eprintln!("Enter a value between 1 and {option_count}");
300 }
301 Err(_) => {
302 eprintln!("Enter an integer value.");
303 }
304 }
305 }
306}
307
308#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
309pub enum Parser {
310 #[default]
311 TreeSitter,
312 ViaConjure,
313}
314
315impl Display for Parser {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 match self {
318 Parser::TreeSitter => write!(f, "tree-sitter"),
319 Parser::ViaConjure => write!(f, "via-conjure"),
320 }
321 }
322}
323
324impl FromStr for Parser {
325 type Err = String;
326
327 fn from_str(s: &str) -> Result<Self, Self::Err> {
328 match s.trim().to_ascii_lowercase().as_str() {
329 "tree-sitter" => Ok(Parser::TreeSitter),
330 "via-conjure" => Ok(Parser::ViaConjure),
331 other => Err(format!(
332 "unknown parser: {other}; expected one of: tree-sitter, via-conjure"
333 )),
334 }
335 }
336}
337
338thread_local! {
339 static CURRENT_PARSER: Cell<Option<Parser>> = const { Cell::new(None) };
343}
344
345pub fn set_current_parser(parser: Parser) {
346 CURRENT_PARSER.with(|current| current.set(Some(parser)));
347}
348
349pub fn current_parser() -> Parser {
350 CURRENT_PARSER.with(|current| {
351 current.get().unwrap_or_else(|| {
352 bug!("current parser not set for this thread; call set_current_parser first")
354 })
355 })
356}
357
358#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
359pub struct RewriteConfig {
360 pub prefilter: bool,
362 pub worklist: bool,
364}
365
366impl RewriteConfig {
367 pub const fn baseline() -> Self {
368 Self {
369 prefilter: false,
370 worklist: false,
371 }
372 }
373
374 pub const fn optimised() -> Self {
375 Self {
376 prefilter: true,
377 worklist: true,
378 }
379 }
380
381 pub const fn is_baseline(self) -> bool {
382 !self.prefilter && !self.worklist
383 }
384
385 pub const fn is_optimised(self) -> bool {
386 self.prefilter && self.worklist
387 }
388}
389
390impl Default for RewriteConfig {
391 fn default() -> Self {
392 Self::optimised()
393 }
394}
395
396impl Display for RewriteConfig {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 if self.is_optimised() {
399 write!(f, "optimised")
400 } else if self.is_baseline() {
401 write!(f, "baseline")
402 } else {
403 let mut options = vec!["baseline"];
404 if self.prefilter {
405 options.push("prefilter");
406 }
407 if self.worklist {
408 options.push("worklist");
409 }
410 write!(f, "{}", options.join("+"))
411 }
412 }
413}
414
415impl FromStr for RewriteConfig {
416 type Err = String;
417
418 fn from_str(s: &str) -> Result<Self, Self::Err> {
419 let trimmed = s.trim().to_ascii_lowercase();
420 match trimmed.as_str() {
421 "baseline" => return Ok(Self::baseline()),
422 "optimised" => return Ok(Self::optimised()),
423 _ => {}
424 }
425
426 if !trimmed.starts_with("baseline+") {
427 return Err(format!(
428 "unknown rewrite config: {trimmed}; expected baseline, optimised, or baseline plus '+' separated prefilter/worklist options"
429 ));
430 }
431
432 let mut config = Self::baseline();
433 let mut baseline_seen = false;
434 let mut prefilter_seen = false;
435 let mut worklist_seen = false;
436 let mut option_seen = false;
437
438 for token in trimmed.split('+') {
439 match token {
440 "" => {}
441 "baseline" => {
442 if baseline_seen {
443 return Err("duplicate rewrite option 'baseline'".to_string());
444 }
445 baseline_seen = true;
446 }
447 "prefilter" => {
448 if prefilter_seen {
449 return Err("duplicate rewrite option 'prefilter'".to_string());
450 }
451 config.prefilter = true;
452 prefilter_seen = true;
453 option_seen = true;
454 }
455 "worklist" => {
456 if worklist_seen {
457 return Err("duplicate rewrite option 'worklist'".to_string());
458 }
459 config.worklist = true;
460 worklist_seen = true;
461 option_seen = true;
462 }
463 other => {
464 return Err(format!(
465 "unknown rewrite option '{other}'; expected baseline, optimised, or a '+' separated combination of: prefilter, worklist"
466 ));
467 }
468 }
469 }
470
471 if !option_seen {
472 return Err(
473 "rewrite config 'baseline+' must include at least one of: prefilter, worklist"
474 .to_string(),
475 );
476 }
477
478 Ok(config)
479 }
480}
481
482#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
483pub enum Rewriter {
484 Rewrite(RewriteConfig),
485}
486
487impl Default for Rewriter {
488 fn default() -> Self {
489 Self::Rewrite(RewriteConfig::optimised())
490 }
491}
492
493thread_local! {
494 static CURRENT_REWRITER: Cell<Option<Rewriter>> = const { Cell::new(None) };
498}
499
500impl Display for Rewriter {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 match self {
503 Rewriter::Rewrite(config) => write!(f, "{config}"),
504 }
505 }
506}
507
508impl FromStr for Rewriter {
509 type Err = String;
510
511 fn from_str(s: &str) -> Result<Self, Self::Err> {
512 let trimmed = s.trim().to_ascii_lowercase();
513 match trimmed.as_str() {
514 "baseline" | "optimised" => Ok(Rewriter::Rewrite(trimmed.parse()?)),
515 other => {
516 if other.contains('+') {
517 return Ok(Rewriter::Rewrite(other.parse()?));
518 }
519
520 Err(format!(
521 "unknown rewriter: {other}; expected baseline, optimised, or baseline plus '+' separated prefilter/worklist options"
522 ))
523 }
524 }
525 }
526}
527
528pub fn set_current_rewriter(rewriter: Rewriter) {
529 CURRENT_REWRITER.with(|current| current.set(Some(rewriter)));
530}
531
532pub fn current_rewriter() -> Rewriter {
533 CURRENT_REWRITER.with(|current| {
534 current.get().unwrap_or_else(|| {
535 bug!("current rewriter not set for this thread; call set_current_rewriter first")
537 })
538 })
539}
540
541#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
542pub enum QuantifiedExpander {
543 Auto,
544 Native,
545 ViaSolver,
546 ViaSolverAc,
547}
548
549impl Display for QuantifiedExpander {
550 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
551 match self {
552 QuantifiedExpander::Auto => write!(f, "auto"),
553 QuantifiedExpander::Native => write!(f, "native"),
554 QuantifiedExpander::ViaSolver => write!(f, "via-solver"),
555 QuantifiedExpander::ViaSolverAc => write!(f, "via-solver-ac"),
556 }
557 }
558}
559
560impl FromStr for QuantifiedExpander {
561 type Err = String;
562
563 fn from_str(s: &str) -> Result<Self, Self::Err> {
564 match s.trim().to_ascii_lowercase().as_str() {
565 "auto" => Ok(QuantifiedExpander::Auto),
566 "native" => Ok(QuantifiedExpander::Native),
567 "via-solver" => Ok(QuantifiedExpander::ViaSolver),
568 "via-solver-ac" => Ok(QuantifiedExpander::ViaSolverAc),
569 _ => Err(format!(
570 "unknown comprehension expander: {s}; expected one of: \
571 auto, native, via-solver, via-solver-ac"
572 )),
573 }
574 }
575}
576
577thread_local! {
578 static COMPREHENSION_EXPANDER: Cell<Option<QuantifiedExpander>> = const { Cell::new(None) };
582}
583
584pub fn set_comprehension_expander(expander: QuantifiedExpander) {
585 COMPREHENSION_EXPANDER.with(|current| current.set(Some(expander)));
586}
587
588pub fn comprehension_expander() -> QuantifiedExpander {
589 COMPREHENSION_EXPANDER.with(|current| {
590 current.get().unwrap_or_else(|| {
591 bug!(
593 "comprehension expander not set for this thread; call set_comprehension_expander first"
594 )
595 })
596 })
597}
598
599#[derive(
600 Debug,
601 EnumIter,
602 StrumDisplay,
603 PartialEq,
604 Eq,
605 Hash,
606 Clone,
607 Copy,
608 Serialize,
609 Deserialize,
610 JsonSchema,
611)]
612pub enum SolverFamily {
613 Minion,
614 Sat,
615 Z3,
616}
617
618thread_local! {
619 static CURRENT_SOLVER_FAMILY: Cell<Option<SolverFamily>> = const { Cell::new(None) };
623}
624
625pub const DEFAULT_MINION_DISCRETE_THRESHOLD: usize = 10;
626
627thread_local! {
628 static MINION_DISCRETE_THRESHOLD: Cell<Option<usize>> = const { Cell::new(None) };
633
634 static RULE_TRACE_ENABLED: Cell<bool> = const { Cell::new(false) };
639
640 static DEFAULT_RULE_TRACE_ENABLED: Cell<bool> = const { Cell::new(false) };
642
643 static RULE_ATTEMPT_TRACE_ENABLED: Cell<bool> = const { Cell::new(false) };
645
646 static RULE_TRACE_AGGREGATES_ENABLED: Cell<bool> = const { Cell::new(false) };
648}
649
650pub fn set_current_solver_family(solver_family: SolverFamily) {
651 CURRENT_SOLVER_FAMILY.with(|current| current.set(Some(solver_family)));
652}
653
654struct SolverFamilyGuard(Option<SolverFamily>);
656
657impl Drop for SolverFamilyGuard {
658 fn drop(&mut self) {
659 CURRENT_SOLVER_FAMILY.with(|current| current.set(self.0));
660 }
661}
662
663pub fn with_solver_family<T>(solver_family: SolverFamily, f: impl FnOnce() -> T) -> T {
669 let previous = CURRENT_SOLVER_FAMILY.with(|current| current.replace(Some(solver_family)));
670 let _guard = SolverFamilyGuard(previous);
671 f()
672}
673
674pub fn try_current_solver_family() -> Option<SolverFamily> {
679 CURRENT_SOLVER_FAMILY.with(|current| current.get())
680}
681
682pub fn current_solver_family() -> SolverFamily {
683 CURRENT_SOLVER_FAMILY.with(|current| {
684 current.get().unwrap_or_else(|| {
685 bug!(
687 "current solver family not set for this thread; call set_current_solver_family first"
688 )
689 })
690 })
691}
692
693pub fn ints_need_representation() -> bool {
701 matches!(
702 try_current_solver_family(),
703 Some(SolverFamily::Sat | SolverFamily::Z3)
704 )
705}
706
707pub fn set_minion_discrete_threshold(threshold: Option<usize>) {
709 MINION_DISCRETE_THRESHOLD.with(|current| current.set(threshold));
710}
711
712pub fn select_minion_discrete_threshold() -> usize {
715 if let Some(threshold) = MINION_DISCRETE_THRESHOLD.with(Cell::get) {
716 return threshold;
717 }
718 let thresholds = [DEFAULT_MINION_DISCRETE_THRESHOLD, 0, usize::MAX];
719 let labels = [
720 "minion-discrete-threshold=10",
721 "minion-discrete-threshold=0",
722 "minion-discrete-threshold=unlimited",
723 ];
724 let index = match heuristic() {
725 Heuristic::First | Heuristic::Compact => 0,
726 Heuristic::Random => next_heuristic_random_index(thresholds.len()),
727 Heuristic::Interactive => next_heuristic_interactive_index(&labels),
728 Heuristic::All => next_heuristic_all_index(&labels),
729 };
730 thresholds[index]
731}
732
733pub fn set_rule_trace_enabled(enabled: bool) {
734 RULE_TRACE_ENABLED.with(|current| current.set(enabled));
735}
736
737pub fn rule_trace_enabled() -> bool {
738 RULE_TRACE_ENABLED.with(|current| current.get())
739}
740
741pub fn set_default_rule_trace_enabled(enabled: bool) {
742 DEFAULT_RULE_TRACE_ENABLED.with(|current| current.set(enabled));
743}
744
745pub fn default_rule_trace_enabled() -> bool {
746 DEFAULT_RULE_TRACE_ENABLED.with(|current| current.get())
747}
748
749pub fn set_rule_attempt_trace_enabled(enabled: bool) {
750 RULE_ATTEMPT_TRACE_ENABLED.with(|current| current.set(enabled));
751}
752
753pub fn rule_attempt_trace_enabled() -> bool {
754 RULE_ATTEMPT_TRACE_ENABLED.with(|current| current.get())
755}
756
757pub fn set_rule_trace_aggregates_enabled(enabled: bool) {
758 RULE_TRACE_AGGREGATES_ENABLED.with(|current| current.set(enabled));
759}
760
761pub fn rule_trace_aggregates_enabled() -> bool {
762 RULE_TRACE_AGGREGATES_ENABLED.with(|current| current.get())
763}
764
765pub fn configured_rule_trace_enabled() -> bool {
766 default_rule_trace_enabled() || rule_attempt_trace_enabled() || rule_trace_aggregates_enabled()
767}
768
769impl FromStr for SolverFamily {
770 type Err = String;
771
772 fn from_str(s: &str) -> Result<Self, Self::Err> {
773 match s.trim().to_ascii_lowercase().as_str() {
774 "minion" => Ok(SolverFamily::Minion),
775 "sat" => Ok(SolverFamily::Sat),
776 "z3" => Ok(SolverFamily::Z3),
777 other => Err(format!(
778 "unknown solver '{other}', expected one of: minion, sat, z3"
779 )),
780 }
781 }
782}
783
784impl SolverFamily {
785 pub fn as_str(&self) -> String {
786 match self {
787 SolverFamily::Minion => "minion".to_owned(),
788 SolverFamily::Sat => "sat".to_owned(),
789 SolverFamily::Z3 => "z3".to_owned(),
790 }
791 }
792}
793
794#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
795pub struct SolverArgs {
796 pub timeout_ms: Option<u64>,
797}
798
799#[cfg(test)]
800mod tests {
801 use super::{
802 Channelling, Heuristic, QuantifiedExpander, RewriteConfig, Rewriter, SolverFamily,
803 begin_heuristic_all_choices, clear_heuristic_responses, heuristic_all_choices,
804 next_heuristic_all_index, next_heuristic_interactive_index, next_heuristic_random_index,
805 set_current_solver_family, set_heuristic_responses, set_heuristic_seed,
806 try_current_solver_family, with_solver_family,
807 };
808 use std::str::FromStr;
809
810 #[test]
811 fn compact_is_the_default_heuristic() {
812 assert_eq!(Heuristic::default(), Heuristic::Compact);
813 }
814
815 #[test]
816 fn scoped_solver_family_restores_the_outer_solver() {
817 set_current_solver_family(SolverFamily::Z3);
818
819 with_solver_family(SolverFamily::Minion, || {
820 assert_eq!(try_current_solver_family(), Some(SolverFamily::Minion));
821 });
822
823 assert_eq!(try_current_solver_family(), Some(SolverFamily::Z3));
824 }
825
826 #[test]
827 fn parses_answer_heuristics_and_channelling() {
828 assert_eq!(Heuristic::from_str("f"), Ok(Heuristic::First));
829 assert_eq!(Heuristic::from_str("random"), Ok(Heuristic::Random));
830 assert_eq!(Heuristic::from_str("c"), Ok(Heuristic::Compact));
831 assert_eq!(Heuristic::from_str("i"), Ok(Heuristic::Interactive));
832 assert_eq!(
833 Heuristic::from_str("interactive"),
834 Ok(Heuristic::Interactive)
835 );
836 assert_eq!(Heuristic::from_str("x"), Ok(Heuristic::All));
837 assert_eq!(Channelling::from_str("no"), Ok(Channelling::No));
838 assert_eq!(Channelling::from_str("yes"), Ok(Channelling::Yes));
839 assert_eq!(Channelling::from_str("uniform"), Ok(Channelling::Uniform));
840 assert_eq!(Channelling::Uniform.to_string(), "uniform");
841 }
842
843 #[test]
844 fn parses_auto_comprehension_expander() {
845 assert_eq!(
846 QuantifiedExpander::from_str("auto"),
847 Ok(QuantifiedExpander::Auto)
848 );
849 assert_eq!(QuantifiedExpander::Auto.to_string(), "auto");
850 }
851
852 #[test]
853 fn interactive_heuristic_consumes_one_based_responses() {
854 set_heuristic_responses(vec![2, 1]);
855 assert_eq!(next_heuristic_interactive_index(&["left", "right"]), 1);
856 assert_eq!(next_heuristic_interactive_index(&["a", "b", "c"]), 0);
857 clear_heuristic_responses();
858 }
859
860 #[test]
861 fn random_heuristic_is_reproducible_from_seed() {
862 set_heuristic_seed(42);
863 let first: Vec<_> = (0..8).map(|_| next_heuristic_random_index(7)).collect();
864 set_heuristic_seed(42);
865 let second: Vec<_> = (0..8).map(|_| next_heuristic_random_index(7)).collect();
866 assert_eq!(first, second);
867 }
868
869 #[test]
870 fn all_heuristic_replays_and_records_choices() {
871 begin_heuristic_all_choices(vec![1]);
872 assert_eq!(next_heuristic_all_index(&["left", "right"]), 1);
873 assert_eq!(
874 heuristic_all_choices()[0].options,
875 vec!["left".to_string(), "right".to_string()]
876 );
877 }
878
879 #[test]
880 fn parses_rewrite_option_combinations() {
881 assert_eq!(
882 RewriteConfig::from_str("baseline").unwrap(),
883 RewriteConfig {
884 prefilter: false,
885 worklist: false,
886 }
887 );
888 assert_eq!(
889 RewriteConfig::from_str("baseline+prefilter").unwrap(),
890 RewriteConfig {
891 prefilter: true,
892 worklist: false,
893 }
894 );
895 assert_eq!(
896 RewriteConfig::from_str("baseline+worklist").unwrap(),
897 RewriteConfig {
898 prefilter: false,
899 worklist: true,
900 }
901 );
902 assert_eq!(
903 RewriteConfig::from_str("baseline+prefilter+worklist").unwrap(),
904 RewriteConfig::optimised()
905 );
906 assert!(RewriteConfig::from_str("+dirty").is_err());
907 assert!(RewriteConfig::from_str("baseline+dirty").is_err());
908 assert!(RewriteConfig::from_str("baseline+cache").is_err());
909 assert!(RewriteConfig::from_str("baseline+rulememo").is_err());
910 assert!(RewriteConfig::from_str("baseline+candidateindex").is_err());
911 assert!(RewriteConfig::from_str("baseline+dirtyqueues").is_err());
912 assert!(RewriteConfig::from_str("baseline+rule-memo").is_err());
913 assert!(RewriteConfig::from_str("baseline+candidate-index").is_err());
914 assert!(RewriteConfig::from_str("baseline+candidate-node-index").is_err());
915 assert!(RewriteConfig::from_str("baseline+dirty-node-queues").is_err());
916 }
917
918 #[test]
919 fn optimised_rewrite_config_enables_fast_default_options() {
920 assert_eq!(
921 RewriteConfig::from_str("optimised").unwrap(),
922 RewriteConfig {
923 prefilter: true,
924 worklist: true,
925 }
926 );
927 }
928
929 #[test]
930 fn displays_rewrite_option_combinations() {
931 assert_eq!(RewriteConfig::baseline().to_string(), "baseline");
932 assert_eq!(
933 RewriteConfig {
934 prefilter: true,
935 worklist: false,
936 }
937 .to_string(),
938 "baseline+prefilter"
939 );
940 assert_eq!(
941 RewriteConfig {
942 prefilter: false,
943 worklist: true,
944 }
945 .to_string(),
946 "baseline+worklist"
947 );
948 assert_eq!(RewriteConfig::optimised().to_string(), "optimised");
949 assert_eq!(
950 RewriteConfig::from_str("baseline+prefilter+worklist")
951 .unwrap()
952 .to_string(),
953 "optimised"
954 );
955 }
956
957 #[test]
958 fn parses_rewriter_option_combinations() {
959 assert_eq!(
960 Rewriter::from_str("baseline+prefilter").unwrap(),
961 Rewriter::Rewrite(RewriteConfig {
962 prefilter: true,
963 worklist: false,
964 })
965 );
966 assert_eq!(
967 Rewriter::from_str("baseline+prefilter+worklist").unwrap(),
968 Rewriter::Rewrite(RewriteConfig::optimised())
969 );
970 assert!(Rewriter::from_str("+dirty").is_err());
971 assert!(Rewriter::from_str("dirty").is_err());
972 assert!(Rewriter::from_str("baseline+dirty").is_err());
973 }
974}
975
976pub fn set_amo_encoding(encoding: Option<crate::ast::sat_decision::AmoEncoding>) {
978 AMO_ENCODING.with(|current| current.set(encoding));
979}
980pub fn amo_encoding() -> Option<crate::ast::sat_decision::AmoEncoding> {
982 AMO_ENCODING.with(Cell::get)
983}
984
985pub fn set_cardinality_encoding(encoding: Option<crate::ast::sat_decision::CardinalityEncoding>) {
987 CARDINALITY_ENCODING.with(|current| current.set(encoding));
988}
989pub fn cardinality_encoding() -> Option<crate::ast::sat_decision::CardinalityEncoding> {
991 CARDINALITY_ENCODING.with(Cell::get)
992}
993
994pub fn set_pb_encoding(encoding: Option<crate::ast::sat_decision::PbEncoding>) {
996 PB_ENCODING.with(|current| current.set(encoding));
997}
998pub fn pb_encoding() -> Option<crate::ast::sat_decision::PbEncoding> {
1000 PB_ENCODING.with(Cell::get)
1001}
1002
1003pub fn set_alldifferent_encoding(encoding: Option<crate::ast::sat_decision::AllDifferentEncoding>) {
1005 ALLDIFFERENT_ENCODING.with(|current| current.set(encoding));
1006}
1007pub fn alldifferent_encoding() -> Option<crate::ast::sat_decision::AllDifferentEncoding> {
1009 ALLDIFFERENT_ENCODING.with(Cell::get)
1010}
1011
1012pub fn set_table_encoding(encoding: Option<crate::ast::sat_decision::TableEncoding>) {
1014 TABLE_ENCODING.with(|current| current.set(encoding));
1015}
1016pub fn table_encoding() -> Option<crate::ast::sat_decision::TableEncoding> {
1018 TABLE_ENCODING.with(Cell::get)
1019}
1020
1021pub fn set_element_encoding(encoding: Option<crate::ast::sat_decision::ElementEncoding>) {
1023 ELEMENT_ENCODING.with(|current| current.set(encoding));
1024}
1025pub fn element_encoding() -> Option<crate::ast::sat_decision::ElementEncoding> {
1027 ELEMENT_ENCODING.with(Cell::get)
1028}