1
/************************************************************************/
2
/*        Rules for translating to Minion-supported constraints         */
3
/************************************************************************/
4

            
5
use crate::metadata::Metadata;
6
use crate::rule_engine::{
7
    register_rule, register_rule_set, ApplicationError, ApplicationResult, Reduction,
8
};
9
use crate::rules::extra_check;
10
use crate::{
11
    ast::{
12
        DecisionVariable, Domain,
13
        Expression::{self as Expr, *},
14
        Factor::{self, *},
15
        Literal::*,
16
        SymbolTable,
17
    },
18
    bug,
19
    rules::utils::{exprs_to_conjunction, is_factor},
20
};
21

            
22
use crate::solver::SolverFamily;
23
use crate::Model;
24
use uniplate::Uniplate;
25
use ApplicationError::RuleNotApplicable;
26

            
27
use super::utils::{is_flat, to_aux_var};
28

            
29
register_rule_set!("Minion", 100, ("Base"), (SolverFamily::Minion));
30

            
31
#[register_rule(("Minion", 4200))]
32
8222390
fn introduce_diveq(expr: &Expr, _: &Model) -> ApplicationResult {
33
8222390
    // div = val
34
8222390
    let val: Factor;
35
8222390
    let div: Expr;
36
8222390
    let meta: Metadata;
37
8222390

            
38
8222390
    match expr.clone() {
39
4539
        Expr::Eq(m, a, b) => {
40
4539
            meta = m;
41
4539
            if let Some(f) = a.as_factor() {
42
850
                // val = div
43
850
                val = f;
44
850
                div = *b;
45
3689
            } else if let Some(f) = b.as_factor() {
46
3689
                // div = val
47
3689
                val = f;
48
3689
                div = *a;
49
3689
            } else {
50
                return Err(RuleNotApplicable);
51
            }
52
        }
53
204
        Expr::AuxDeclaration(m, name, e) => {
54
204
            meta = m;
55
204
            val = name.into();
56
204
            div = *e;
57
204
        }
58
        _ => {
59
8217647
            return Err(RuleNotApplicable);
60
        }
61
    }
62

            
63
4743
    if !(matches!(div, Expr::SafeDiv(_, _, _))) {
64
4420
        return Err(RuleNotApplicable);
65
323
    }
66
323

            
67
323
    let children = div.children();
68
323
    let a = children[0].as_factor().ok_or(RuleNotApplicable)?;
69
323
    let b = children[1].as_factor().ok_or(RuleNotApplicable)?;
70

            
71
272
    Ok(Reduction::pure(DivEq(meta.clone_dirty(), a, b, val)))
72
8222390
}
73

            
74
#[register_rule(("Minion", 4400))]
75
8222390
fn flatten_binop(expr: &Expr, model: &Model) -> ApplicationResult {
76
8222390
    if !matches!(expr, Expr::SafeDiv(_, _, _) | Expr::Neq(_, _, _)) {
77
8221251
        return Err(RuleNotApplicable);
78
1139
    }
79
1139

            
80
1139
    let mut children = expr.children();
81
1139
    debug_assert_eq!(children.len(), 2);
82

            
83
1139
    let mut model = model.clone();
84
1139
    let mut num_changed = 0;
85
1139
    let mut new_tops: Vec<Expr> = vec![];
86

            
87
2278
    for child in children.iter_mut() {
88
2278
        if let Some(aux_var_info) = to_aux_var(child, &model) {
89
170
            model = aux_var_info.model();
90
170
            new_tops.push(aux_var_info.top_level_expr());
91
170
            *child = aux_var_info.as_expr();
92
170
            num_changed += 1;
93
2108
        }
94
    }
95

            
96
1139
    if num_changed == 0 {
97
969
        return Err(RuleNotApplicable);
98
170
    }
99
170

            
100
170
    let expr = expr.with_children(children);
101
170
    let new_top = exprs_to_conjunction(&new_tops).unwrap_or_else(|| {
102
        bug!("rules::minion::flatten_binop : new_tops could be combined with And!")
103
170
    });
104
170

            
105
170
    Ok(Reduction::new(expr, new_top, model.variables))
106
8222390
}
107

            
108
#[register_rule(("Minion", 4400))]
109
8222390
fn flatten_vecop(expr: &Expr, model: &Model) -> ApplicationResult {
110
8222390
    if !matches!(expr, Expr::Sum(_, _)) {
111
8222220
        return Err(RuleNotApplicable);
112
170
    }
113
170

            
114
170
    let mut children = expr.children();
115
170

            
116
170
    let mut model = model.clone();
117
170
    let mut num_changed = 0;
118
170
    let mut new_tops: Vec<Expr> = vec![];
119

            
120
476
    for child in children.iter_mut() {
121
476
        if let Some(aux_var_info) = to_aux_var(child, &model) {
122
            model = aux_var_info.model();
123
            new_tops.push(aux_var_info.top_level_expr());
124
            *child = aux_var_info.as_expr();
125
            num_changed += 1;
126
476
        }
127
    }
128

            
129
170
    if num_changed == 0 {
130
170
        return Err(RuleNotApplicable);
131
    }
132

            
133
    let expr = expr.with_children(children);
134
    let new_top = exprs_to_conjunction(&new_tops).unwrap_or_else(|| {
135
        bug!("rules::minion::flatten_vecop : new_tops could be combined with And!")
136
    });
137

            
138
    Ok(Reduction::new(new_top, expr, model.variables))
139
8222390
}
140

            
141
#[register_rule(("Minion", 4400))]
142
8222390
fn flatten_eq(expr: &Expr, model: &Model) -> ApplicationResult {
143
8222390
    if !matches!(expr, Expr::Eq(_, _, _)) {
144
8217851
        return Err(RuleNotApplicable);
145
4539
    }
146
4539

            
147
4539
    let mut children = expr.children();
148
4539
    debug_assert_eq!(children.len(), 2);
149

            
150
4539
    let mut model = model.clone();
151
4539
    let mut num_changed = 0;
152
4539
    let mut new_tops: Vec<Expr> = vec![];
153

            
154
9078
    for child in children.iter_mut() {
155
9078
        if let Some(aux_var_info) = to_aux_var(child, &model) {
156
119
            model = aux_var_info.model();
157
119
            new_tops.push(aux_var_info.top_level_expr());
158
119
            *child = aux_var_info.as_expr();
159
119
            num_changed += 1;
160
8959
        }
161
    }
162

            
163
    // eq: both sides have to be non flat for the rule to be applicable!
164
4539
    if num_changed != 2 {
165
4539
        return Err(RuleNotApplicable);
166
    }
167

            
168
    let expr = expr.with_children(children);
169
    let new_top = exprs_to_conjunction(&new_tops)
170
        .unwrap_or_else(|| bug!("rules::minion::flatten_eq: new_tops could be combined with And!"));
171

            
172
    Ok(Reduction::new(new_top, expr, model.variables))
173
8222390
}
174

            
175
3434
fn is_nested_sum(exprs: &Vec<Expr>) -> bool {
176
13158
    for e in exprs {
177
9894
        if let Sum(_, _) = e {
178
170
            return true;
179
9724
        }
180
    }
181
3264
    false
182
3434
}
183

            
184
/**
185
 * Helper function to get the vector of expressions from a sum (or error if it's a nested sum - we need to flatten it first)
186
 */
187
6579
fn sum_to_vector(expr: &Expr) -> Result<Vec<Expr>, ApplicationError> {
188
6579
    match expr {
189
3434
        Sum(_, exprs) => {
190
3434
            if is_nested_sum(exprs) {
191
170
                Err(RuleNotApplicable)
192
            } else {
193
3264
                Ok(exprs.clone())
194
            }
195
        }
196
3145
        _ => Err(RuleNotApplicable),
197
    }
198
6579
}
199

            
200
// /**
201
//  * Convert an Eq to a conjunction of Geq and Leq:
202
//  * ```text
203
//  * a = b => a >= b && a <= b
204
//  * ```
205
//  */
206
// #[register_rule(("Minion", 100))]
207
// fn eq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
208
//     match expr {
209
//         Expr::Eq(metadata, a, b) => Ok(Reduction::pure(Expr::And(
210
//             metadata.clone_dirty(),
211
//             vec![
212
//                 Expr::Geq(metadata.clone_dirty(), a.clone(), b.clone()),
213
//                 Expr::Leq(metadata.clone_dirty(), a.clone(), b.clone()),
214
//             ],
215
//         ))),
216
//         _ => Err(ApplicationError::RuleNotApplicable),
217
//     }
218
// }
219

            
220
/**
221
 * Convert a Geq to a SumGeq if the left hand side is a sum:
222
 * ```text
223
1
 * sum([a, b, c]) >= d => sum_geq([a, b, c], d)
224
1
 * ```
225
1
 */
226
#[register_rule(("Minion", 4400))]
227
8222407
fn flatten_sum_geq(expr: &Expr, _: &Model) -> ApplicationResult {
228
8222407
    match expr {
229
272
        Geq(metadata, a, b) => {
230
272
            let exprs = sum_to_vector(a)?;
231
34
            Ok(Reduction::pure(SumGeq(
232
34
                metadata.clone_dirty(),
233
34
                exprs,
234
34
                b.clone(),
235
34
            )))
236
        }
237
8222135
        _ => Err(RuleNotApplicable),
238
    }
239
8222407
}
240

            
241
/**
242
 * Convert a Leq to a SumLeq if the left hand side is a sum:
243
 * ```text
244
1
 * sum([a, b, c]) <= d => sum_leq([a, b, c], d)
245
1
 * ```
246
1
 */
247
#[register_rule(("Minion", 4400))]
248
8222407
fn sum_leq_to_sumleq(expr: &Expr, _: &Model) -> ApplicationResult {
249
8222407
    match expr {
250
374
        Leq(metadata, a, b) => {
251
374
            let exprs = sum_to_vector(a)?;
252
68
            Ok(Reduction::pure(SumLeq(
253
68
                metadata.clone_dirty(),
254
68
                exprs,
255
68
                b.clone(),
256
68
            )))
257
        }
258
8222033
        _ => Err(RuleNotApplicable),
259
    }
260
8222407
}
261

            
262
/**
263
 * Convert a 'Eq(Sum([...]))' to a SumEq
264
 * ```text
265
1
 * eq(sum([a, b]), c) => sumeq([a, b], c)
266
1
 * ```
267
1
*/
268
#[register_rule(("Minion", 4400))]
269
8222390
fn sum_eq_to_sumeq(expr: &Expr, _: &Model) -> ApplicationResult {
270
8222390
    match expr {
271
4539
        Eq(metadata, a, b) => {
272
4539
            if let Ok(exprs) = sum_to_vector(a) {
273
3145
                Ok(Reduction::pure(SumEq(
274
3145
                    metadata.clone_dirty(),
275
3145
                    exprs,
276
3145
                    b.clone(),
277
3145
                )))
278
1394
            } else if let Ok(exprs) = sum_to_vector(b) {
279
17
                Ok(Reduction::pure(SumEq(
280
17
                    metadata.clone_dirty(),
281
17
                    exprs,
282
17
                    a.clone(),
283
17
                )))
284
            } else {
285
1377
                Err(RuleNotApplicable)
286
            }
287
        }
288
8217851
        _ => Err(RuleNotApplicable),
289
    }
290
8222390
}
291

            
292
/**
293
 * Convert a `SumEq` to an `And(SumGeq, SumLeq)`
294
 * This is a workaround for Minion not having support for a flat "equals" operation on sums
295
 * ```text
296
 * sumeq([a, b], c) -> watched_and({
297
 *   sumleq([a, b], c),
298
 *   sumgeq([a, b], c)
299
1
 * })
300
1
 * ```
301
1
 * I. e.
302
 * ```text
303
 * ((a + b) >= c) && ((a + b) <= c)
304
1
 * a + b = c
305
1
 * ```
306
1
 */
307
#[register_rule(("Minion", 4400))]
308
8222390
fn sumeq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
309
8222390
    match expr {
310
3196
        SumEq(_metadata, exprs, eq_to) => Ok(Reduction::pure(And(
311
3196
            Metadata::new(),
312
3196
            vec![
313
3196
                SumGeq(Metadata::new(), exprs.clone(), Box::from(*eq_to.clone())),
314
3196
                SumLeq(Metadata::new(), exprs.clone(), Box::from(*eq_to.clone())),
315
3196
            ],
316
3196
        ))),
317
8219194
        _ => Err(RuleNotApplicable),
318
    }
319
8222390
}
320

            
321
/**
322
* Convert a Lt to an Ineq:
323

            
324
* ```text
325
1
* a < b ~> a <= b -1 ~> ineq(a,b,-1)
326
1
* ```
327
1
*/
328
#[register_rule(("Minion", 4100))]
329
8222407
fn lt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
330
8222407
    match expr {
331
3111
        Lt(metadata, a, b) => Ok(Reduction::pure(Ineq(
332
3111
            metadata.clone_dirty(),
333
3111
            a.clone(),
334
3111
            b.clone(),
335
3111
            Box::new(FactorE(Metadata::new(), Literal(Int(-1)))),
336
3111
        ))),
337
8219296
        _ => Err(RuleNotApplicable),
338
    }
339
8222407
}
340

            
341
/**
342
* Convert a Gt to an Ineq:
343
*
344
* ```text
345
1
* a > b ~> b <= a -1 ~> ineq(b,a,-1)
346
1
* ```
347
1
*/
348
#[register_rule(("Minion", 4100))]
349
8222390
fn gt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
350
8222390
    match expr {
351
        Gt(metadata, a, b) => Ok(Reduction::pure(Ineq(
352
            metadata.clone_dirty(),
353
            b.clone(),
354
            a.clone(),
355
            Box::new(FactorE(Metadata::new(), Literal(Int(-1)))),
356
        ))),
357
8222390
        _ => Err(RuleNotApplicable),
358
    }
359
8222390
}
360

            
361
/**
362
* Convert a Geq to an Ineq:
363
*
364
* ```text
365
1
* a >= b ~> b <= a + 0 ~> ineq(b,a,0)
366
1
* ```
367
1
*/
368
#[register_rule(("Minion", 4100))]
369
8222390
fn geq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
370
8222390
    match expr {
371
255
        Geq(metadata, a, b) => Ok(Reduction::pure(Ineq(
372
255
            metadata.clone_dirty(),
373
255
            b.clone(),
374
255
            a.clone(),
375
255
            Box::new(FactorE(Metadata::new(), Literal(Int(0)))),
376
255
        ))),
377
8222135
        _ => Err(RuleNotApplicable),
378
    }
379
8222390
}
380

            
381
/**
382
* Convert a Leq to an Ineq:
383
*
384
* ```text
385
1
* a <= b ~> a <= b + 0 ~> ineq(a,b,0)
386
1
* ```
387
1
*/
388
#[register_rule(("Minion", 4100))]
389
8222390
fn leq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
390
8222390
    match expr {
391
357
        Leq(metadata, a, b) => Ok(Reduction::pure(Ineq(
392
357
            metadata.clone_dirty(),
393
357
            a.clone(),
394
357
            b.clone(),
395
357
            Box::new(FactorE(Metadata::new(), Literal(Int(0)))),
396
357
        ))),
397
8222033
        _ => Err(RuleNotApplicable),
398
    }
399
8222390
}
400

            
401
/// ```text
402
/// x <= y + k ~> ineq(x,y,k)
403
/// ```
404

            
405
#[register_rule(("Minion",4400))]
406
8222390
fn x_leq_y_plus_k_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
407
8222390
    let Leq(_, x, b) = expr else {
408
8222033
        return Err(RuleNotApplicable);
409
    };
410

            
411
357
    let x @ FactorE(_, Reference(_)) = *x.to_owned() else {
412
119
        return Err(RuleNotApplicable);
413
    };
414

            
415
238
    let Sum(_, c) = *b.to_owned() else {
416
221
        return Err(RuleNotApplicable);
417
    };
418

            
419
17
    let [ref y @ FactorE(_, Reference(_)), ref k @ FactorE(_, Literal(_))] = c[..] else {
420
        return Err(RuleNotApplicable);
421
    };
422

            
423
17
    Ok(Reduction::pure(Ineq(
424
17
        expr.get_meta().clone_dirty(),
425
17
        Box::new(x),
426
17
        Box::new(y.clone()),
427
17
        Box::new(k.clone()),
428
17
    )))
429
8222390
}
430

            
431
// #[register_rule(("Minion", 99))]
432
// fn eq_to_leq_geq(expr: &Expr, _: &Model) -> ApplicationResult {
433
//     match expr {
434
//         Eq(metadata, a, b) => {
435
//             return Ok(Reduction::pure(Expr::And(
436
//                 metadata.clone(),
437
//                 vec![
438
//                     Expr::Leq(metadata.clone(), a.clone(), b.clone()),
439
//                     Expr::Geq(metadata.clone(), a.clone(), b.clone()),
440
//                 ],
441
//             )));
442
//         }
443
//         _ => Err(RuleNotApplicable),
444
//     }
445
// }
446

            
447
/// Flattening rule that converts boolean variables to watched-literal constraints.
448
///
449
/// For some boolean variable x:
450
/// ```text
451
/// and([x,...]) ~> and([w-literal(x,1),..])
452
///  or([x,...]) ~>  or([w-literal(x,1),..])
453
///  not(x)      ~>  w-literal(x,0)
454
/// ```
455
///
456
/// ## Rationale
457
///
458
/// Minion's watched-and and watched-or constraints only takes other constraints as arguments.
459
///
460
/// This restates boolean variables as the equivalent constraint "SAT if x is true".
461
///
462
#[register_rule(("Minion", 4100))]
463
8222390
fn boolean_literal_to_wliteral(expr: &Expr, mdl: &Model) -> ApplicationResult {
464
    use Domain::BoolDomain;
465
8222390
    match expr {
466
428706
        Or(m, vec) => {
467
428706
            let mut changed = false;
468
428706
            let mut new_vec = Vec::new();
469
2094366
            for expr in vec {
470
1665660
                new_vec.push(match expr {
471
170
                    FactorE(m, Reference(name))
472
170
                        if mdl
473
170
                            .get_domain(name)
474
170
                            .is_some_and(|x| matches!(x, BoolDomain)) =>
475
                    {
476
170
                        changed = true;
477
170
                        WatchedLiteral(m.clone_dirty(), name.clone(), Bool(true))
478
                    }
479
1665490
                    e => e.clone(),
480
                });
481
            }
482

            
483
428706
            if !changed {
484
428604
                return Err(RuleNotApplicable);
485
102
            }
486
102

            
487
102
            Ok(Reduction::pure(Or(m.clone_dirty(), new_vec)))
488
        }
489
19754
        And(m, vec) => {
490
19754
            let mut changed = false;
491
19754
            let mut new_vec = Vec::new();
492
616896
            for expr in vec {
493
597142
                new_vec.push(match expr {
494
85
                    FactorE(m, Reference(name))
495
85
                        if mdl
496
85
                            .get_domain(name)
497
85
                            .is_some_and(|x| matches!(x, BoolDomain)) =>
498
                    {
499
85
                        changed = true;
500
85
                        WatchedLiteral(m.clone_dirty(), name.clone(), Bool(true))
501
                    }
502
597057
                    e => e.clone(),
503
                });
504
            }
505

            
506
19754
            if !changed {
507
19669
                return Err(RuleNotApplicable);
508
85
            }
509
85

            
510
85
            Ok(Reduction::pure(And(m.clone_dirty(), new_vec)))
511
        }
512

            
513
527
        Not(m, expr) => {
514
527
            if let FactorE(_, Reference(name)) = (**expr).clone() {
515
34
                if mdl
516
34
                    .get_domain(&name)
517
34
                    .is_some_and(|x| matches!(x, BoolDomain))
518
                {
519
34
                    return Ok(Reduction::pure(WatchedLiteral(
520
34
                        m.clone_dirty(),
521
34
                        name.clone(),
522
34
                        Bool(false),
523
34
                    )));
524
                }
525
493
            }
526
493
            Err(RuleNotApplicable)
527
        }
528
7773403
        _ => Err(RuleNotApplicable),
529
    }
530
8222390
}
531

            
532
/// Flattening rule for not(X) in Minion, where X is a constraint.
533
///
534
/// ```text
535
/// not(X) ~> reify(X,0)
536
/// ```
537
///
538
/// This rule has lower priority than boolean_literal_to_wliteral so that we can assume that the
539
/// nested expressions are constraints not variables.
540

            
541
#[register_rule(("Minion", 4090))]
542
8222390
fn not_constraint_to_reify(expr: &Expr, _: &Model) -> ApplicationResult {
543
8221897
    if !matches!(expr, Not(_,c) if !matches!(**c, FactorE(_,_))) {
544
8221897
        return Err(RuleNotApplicable);
545
493
    }
546

            
547
493
    let Not(m, e) = expr else {
548
        unreachable!();
549
    };
550

            
551
493
    extra_check! {
552
493
        if !is_flat(e) {
553
442
            return Err(RuleNotApplicable);
554
51
        }
555
    };
556

            
557
51
    Ok(Reduction::pure(Reify(
558
51
        m.clone(),
559
51
        e.clone(),
560
51
        Box::new(FactorE(Metadata::new(), Literal(Bool(false)))),
561
51
    )))
562
8222390
}