1
use std::sync::{Arc, Mutex};
2

            
3
use conjure_cp::{
4
    ast::{
5
        DecisionVariable, DeclarationKind, Expression, Model, Name, comprehension::Comprehension,
6
    },
7
    rule_engine::resolve_rule_sets,
8
    settings::{SolverFamily, current_rewriter},
9
    solver::{Solver, SolverError, adaptors::Minion},
10
};
11

            
12
use super::via_solver_common::{
13
    instantiate_return_expressions_from_values, retain_quantified_solution_values,
14
    rewrite_model_with_configured_rewriter, with_temporary_model,
15
};
16

            
17
/// Expands the comprehension by solving quantified variables with Minion.
18
///
19
/// This returns one expression per assignment to quantified variables that satisfies the static
20
/// guards of the comprehension.
21
63
pub fn expand_via_solver(comprehension: Comprehension) -> Result<Vec<Expression>, SolverError> {
22
63
    let minion = Solver::new(Minion::new());
23
63
    let quantified_vars = comprehension.quantified_vars();
24

            
25
63
    let mut generator_model = comprehension.to_generator_model();
26
63
    materialise_quantified_finds(&mut generator_model, &quantified_vars);
27

            
28
    // only branch on the quantified variables.
29
63
    let generator_model = with_temporary_model(generator_model, Some(quantified_vars.clone()));
30
63
    tracing::trace!(
31
        target: "rule_engine_human",
32
        "Via-solver generator model before rewriting:\n\n{}\n--\n",
33
        generator_model
34
    );
35

            
36
    // call rewrite here as well as in expand_via_solver_ac, just to be consistent
37
63
    let extra_rule_sets = &["Base", "Constant", "Bubble"];
38

            
39
63
    let rule_sets = resolve_rule_sets(SolverFamily::Minion, extra_rule_sets).unwrap();
40
63
    let configured_rewriter = current_rewriter();
41

            
42
63
    let generator_model =
43
63
        rewrite_model_with_configured_rewriter(generator_model, &rule_sets, configured_rewriter);
44

            
45
    // Call the rewriter to rewrite inside the comprehension
46
    //
47
    // The original idea was to let the top level rewriter rewrite the return expression model
48
    // and the generator model. The comprehension wouldn't be expanded until the generator
49
    // model is in valid minion that can be ran, at which point the return expression model
50
    // should also be in valid minion.
51
    //
52
    // By calling the rewriter inside the rule, we no longer wait for the generator model to be
53
    // valid Minion, so we don't get the simplified return model either...
54
    //
55
    // We need to do this as we want to modify the generator model (add the dummy Z's) then
56
    // solve and return in one go.
57
    //
58
    // Comprehensions need a big rewrite soon, as theres lots of sharp edges such as this in
59
    // my original implementation, and I don't think we can fit our new optimisation into it.
60
    // If we wanted to avoid calling the rewriter, we would need to run the first half the rule
61
    // up to adding the return expr to the generator model, yield, then come back later to
62
    // actually solve it?
63

            
64
    // Keep return expressions unreduced until quantified assignments are substituted.
65
    // Rewriting before substitution can change guard structure in ways that are unsafe for
66
    // constant evaluation after instantiation.
67
63
    let return_expression_model =
68
63
        with_temporary_model(comprehension.to_return_expression_model(), None);
69

            
70
63
    let values = {
71
63
        let solver_model = generator_model.clone();
72

            
73
63
        let minion = minion.load_model(solver_model)?;
74

            
75
63
        let values = Arc::new(Mutex::new(Vec::new()));
76
63
        let values_ptr = Arc::clone(&values);
77
63
        let quantified_vars_for_solution = quantified_vars.clone();
78

            
79
        tracing::debug!(model=%generator_model,comprehension=%comprehension,"Minion solving comprehension (solver mode)");
80
3231
        minion.solve(Box::new(move |sols| {
81
            // Only keep quantified assignments; discard solver auxiliaries/locals.
82
3231
            let values = &mut *values_ptr.lock().unwrap();
83
3231
            values.push(retain_quantified_solution_values(
84
3231
                sols,
85
3231
                &quantified_vars_for_solution,
86
            ));
87
3231
            true
88
3231
        }))?;
89

            
90
63
        values.lock().unwrap().clone()
91
    };
92
63
    Ok(instantiate_return_expressions_from_values(
93
63
        values,
94
63
        &return_expression_model,
95
63
        &quantified_vars,
96
63
    ))
97
63
}
98

            
99
63
fn materialise_quantified_finds(model: &mut Model, quantified_vars: &[Name]) {
100
63
    let symbols = model.symbols().clone();
101

            
102
81
    for name in quantified_vars {
103
81
        let Some(mut decl) = symbols.lookup_local(name) else {
104
            continue;
105
        };
106
81
        let Some(domain) = decl.domain() else {
107
            continue;
108
        };
109

            
110
81
        let _ = decl.replace_kind(DeclarationKind::Find(DecisionVariable::new(domain)));
111
    }
112
63
}