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
        Atom::{self, *},
13
        DecisionVariable, Domain,
14
        Expression::{self as Expr, *},
15
        Literal::*,
16
        SymbolTable,
17
    },
18
    bug,
19
    rules::utils::{exprs_to_conjunction, is_atom},
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
8229377
fn introduce_diveq(expr: &Expr, _: &Model) -> ApplicationResult {
33
8229377
    // div = val
34
8229377
    let val: Atom;
35
8229377
    let div: Expr;
36
8229377
    let meta: Metadata;
37
8229377

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

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

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

            
71
272
    Ok(Reduction::pure(DivEqUndefZero(
72
272
        meta.clone_dirty(),
73
272
        a,
74
272
        b,
75
272
        val,
76
272
    )))
77
8229377
}
78

            
79
#[register_rule(("Minion", 4200))]
80
8229377
fn introduce_modeq(expr: &Expr, _: &Model) -> ApplicationResult {
81
8229377
    // div = val
82
8229377
    let val: Atom;
83
8229377
    let div: Expr;
84
8229377
    let meta: Metadata;
85
8229377

            
86
8229377
    match expr.clone() {
87
5168
        Expr::Eq(m, a, b) => {
88
5168
            meta = m;
89
5168
            if let Some(f) = a.as_atom() {
90
1105
                // val = div
91
1105
                val = f;
92
1105
                div = *b;
93
4063
            } else if let Some(f) = b.as_atom() {
94
4063
                // div = val
95
4063
                val = f;
96
4063
                div = *a;
97
4063
            } else {
98
                return Err(RuleNotApplicable);
99
            }
100
        }
101
408
        Expr::AuxDeclaration(m, name, e) => {
102
408
            meta = m;
103
408
            val = name.into();
104
408
            div = *e;
105
408
        }
106
        _ => {
107
8223801
            return Err(RuleNotApplicable);
108
        }
109
    }
110

            
111
5576
    if !(matches!(div, Expr::SafeMod(_, _, _))) {
112
5253
        return Err(RuleNotApplicable);
113
323
    }
114
323

            
115
323
    let children = div.children();
116
323
    let a = children[0].as_atom().ok_or(RuleNotApplicable)?;
117
323
    let b = children[1].as_atom().ok_or(RuleNotApplicable)?;
118

            
119
272
    Ok(Reduction::pure(ModuloEqUndefZero(
120
272
        meta.clone_dirty(),
121
272
        a,
122
272
        b,
123
272
        val,
124
272
    )))
125
8229377
}
126

            
127
#[register_rule(("Minion", 4400))]
128
8229377
fn flatten_binop(expr: &Expr, model: &Model) -> ApplicationResult {
129
8227167
    if !matches!(
130
8229377
        expr,
131
        Expr::SafeDiv(_, _, _) | Expr::Neq(_, _, _) | Expr::SafeMod(_, _, _)
132
    ) {
133
8227167
        return Err(RuleNotApplicable);
134
2210
    }
135
2210

            
136
2210
    let mut children = expr.children();
137
2210
    debug_assert_eq!(children.len(), 2);
138

            
139
2210
    let mut model = model.clone();
140
2210
    let mut num_changed = 0;
141
2210
    let mut new_tops: Vec<Expr> = vec![];
142

            
143
4420
    for child in children.iter_mut() {
144
4420
        if let Some(aux_var_info) = to_aux_var(child, &model) {
145
340
            model = aux_var_info.model();
146
340
            new_tops.push(aux_var_info.top_level_expr());
147
340
            *child = aux_var_info.as_expr();
148
340
            num_changed += 1;
149
4080
        }
150
    }
151

            
152
2210
    if num_changed == 0 {
153
1870
        return Err(RuleNotApplicable);
154
340
    }
155
340

            
156
340
    let expr = expr.with_children(children);
157
340
    let new_top = exprs_to_conjunction(&new_tops).unwrap_or_else(|| {
158
        bug!("rules::minion::flatten_binop : new_tops could be combined with And!")
159
340
    });
160
340

            
161
340
    Ok(Reduction::new(expr, new_top, model.variables))
162
8229377
}
163

            
164
#[register_rule(("Minion", 4400))]
165
8229377
fn flatten_vecop(expr: &Expr, model: &Model) -> ApplicationResult {
166
8229377
    if !matches!(expr, Expr::Sum(_, _)) {
167
8229207
        return Err(RuleNotApplicable);
168
170
    }
169
170

            
170
170
    let mut children = expr.children();
171
170

            
172
170
    let mut model = model.clone();
173
170
    let mut num_changed = 0;
174
170
    let mut new_tops: Vec<Expr> = vec![];
175

            
176
476
    for child in children.iter_mut() {
177
476
        if let Some(aux_var_info) = to_aux_var(child, &model) {
178
            model = aux_var_info.model();
179
            new_tops.push(aux_var_info.top_level_expr());
180
            *child = aux_var_info.as_expr();
181
            num_changed += 1;
182
476
        }
183
    }
184

            
185
170
    if num_changed == 0 {
186
170
        return Err(RuleNotApplicable);
187
    }
188

            
189
    let expr = expr.with_children(children);
190
    let new_top = exprs_to_conjunction(&new_tops).unwrap_or_else(|| {
191
        bug!("rules::minion::flatten_vecop : new_tops could be combined with And!")
192
    });
193

            
194
    Ok(Reduction::new(new_top, expr, model.variables))
195
8229377
}
196

            
197
#[register_rule(("Minion", 4400))]
198
8229377
fn flatten_eq(expr: &Expr, model: &Model) -> ApplicationResult {
199
8229377
    if !matches!(expr, Expr::Eq(_, _, _)) {
200
8224209
        return Err(RuleNotApplicable);
201
5168
    }
202
5168

            
203
5168
    let mut children = expr.children();
204
5168
    debug_assert_eq!(children.len(), 2);
205

            
206
5168
    let mut model = model.clone();
207
5168
    let mut num_changed = 0;
208
5168
    let mut new_tops: Vec<Expr> = vec![];
209

            
210
10336
    for child in children.iter_mut() {
211
10336
        if let Some(aux_var_info) = to_aux_var(child, &model) {
212
238
            model = aux_var_info.model();
213
238
            new_tops.push(aux_var_info.top_level_expr());
214
238
            *child = aux_var_info.as_expr();
215
238
            num_changed += 1;
216
10098
        }
217
    }
218

            
219
    // eq: both sides have to be non flat for the rule to be applicable!
220
5168
    if num_changed != 2 {
221
5168
        return Err(RuleNotApplicable);
222
    }
223

            
224
    let expr = expr.with_children(children);
225
    let new_top = exprs_to_conjunction(&new_tops)
226
        .unwrap_or_else(|| bug!("rules::minion::flatten_eq: new_tops could be combined with And!"));
227

            
228
    Ok(Reduction::new(new_top, expr, model.variables))
229
8229377
}
230

            
231
3434
fn is_nested_sum(exprs: &Vec<Expr>) -> bool {
232
13158
    for e in exprs {
233
9894
        if let Sum(_, _) = e {
234
170
            return true;
235
9724
        }
236
    }
237
3264
    false
238
3434
}
239

            
240
/**
241
 * 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)
242
 */
243
7854
fn sum_to_vector(expr: &Expr) -> Result<Vec<Expr>, ApplicationError> {
244
7854
    match expr {
245
3434
        Sum(_, exprs) => {
246
3434
            if is_nested_sum(exprs) {
247
170
                Err(RuleNotApplicable)
248
            } else {
249
3264
                Ok(exprs.clone())
250
            }
251
        }
252
4420
        _ => Err(RuleNotApplicable),
253
    }
254
7854
}
255

            
256
// /**
257
//  * Convert an Eq to a conjunction of Geq and Leq:
258
//  * ```text
259
//  * a = b => a >= b && a <= b
260
//  * ```
261
//  */
262
// #[register_rule(("Minion", 100))]
263
// fn eq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
264
//     match expr {
265
//         Expr::Eq(metadata, a, b) => Ok(Reduction::pure(Expr::And(
266
//             metadata.clone_dirty(),
267
//             vec![
268
//                 Expr::Geq(metadata.clone_dirty(), a.clone(), b.clone()),
269
//                 Expr::Leq(metadata.clone_dirty(), a.clone(), b.clone()),
270
//             ],
271
//         ))),
272
//         _ => Err(ApplicationError::RuleNotApplicable),
273
//     }
274
// }
275

            
276
/**
277
 * Convert a Geq to a SumGeq if the left hand side is a sum:
278
 * ```text
279
1
 * sum([a, b, c]) >= d => sum_geq([a, b, c], d)
280
1
 * ```
281
1
 */
282
#[register_rule(("Minion", 4400))]
283
8229394
fn flatten_sum_geq(expr: &Expr, _: &Model) -> ApplicationResult {
284
8229394
    match expr {
285
289
        Geq(metadata, a, b) => {
286
289
            let exprs = sum_to_vector(a)?;
287
34
            Ok(Reduction::pure(SumGeq(
288
34
                metadata.clone_dirty(),
289
34
                exprs,
290
34
                b.clone(),
291
34
            )))
292
        }
293
8229105
        _ => Err(RuleNotApplicable),
294
    }
295
8229394
}
296

            
297
/**
298
 * Convert a Leq to a SumLeq if the left hand side is a sum:
299
 * ```text
300
1
 * sum([a, b, c]) <= d => sum_leq([a, b, c], d)
301
1
 * ```
302
1
 */
303
#[register_rule(("Minion", 4400))]
304
8229394
fn sum_leq_to_sumleq(expr: &Expr, _: &Model) -> ApplicationResult {
305
8229394
    match expr {
306
374
        Leq(metadata, a, b) => {
307
374
            let exprs = sum_to_vector(a)?;
308
68
            Ok(Reduction::pure(SumLeq(
309
68
                metadata.clone_dirty(),
310
68
                exprs,
311
68
                b.clone(),
312
68
            )))
313
        }
314
8229020
        _ => Err(RuleNotApplicable),
315
    }
316
8229394
}
317

            
318
/**
319
 * Convert a 'Eq(Sum([...]))' to a SumEq
320
 * ```text
321
1
 * eq(sum([a, b]), c) => sumeq([a, b], c)
322
1
 * ```
323
1
*/
324
#[register_rule(("Minion", 4400))]
325
8229377
fn sum_eq_to_sumeq(expr: &Expr, _: &Model) -> ApplicationResult {
326
8229377
    match expr {
327
5168
        Eq(metadata, a, b) => {
328
5168
            if let Ok(exprs) = sum_to_vector(a) {
329
3145
                Ok(Reduction::pure(SumEq(
330
3145
                    metadata.clone_dirty(),
331
3145
                    exprs,
332
3145
                    b.clone(),
333
3145
                )))
334
2023
            } else if let Ok(exprs) = sum_to_vector(b) {
335
17
                Ok(Reduction::pure(SumEq(
336
17
                    metadata.clone_dirty(),
337
17
                    exprs,
338
17
                    a.clone(),
339
17
                )))
340
            } else {
341
2006
                Err(RuleNotApplicable)
342
            }
343
        }
344
8224209
        _ => Err(RuleNotApplicable),
345
    }
346
8229377
}
347

            
348
/**
349
 * Convert a `SumEq` to an `And(SumGeq, SumLeq)`
350
 * This is a workaround for Minion not having support for a flat "equals" operation on sums
351
 * ```text
352
 * sumeq([a, b], c) -> watched_and({
353
 *   sumleq([a, b], c),
354
 *   sumgeq([a, b], c)
355
1
 * })
356
1
 * ```
357
1
 * I. e.
358
 * ```text
359
 * ((a + b) >= c) && ((a + b) <= c)
360
1
 * a + b = c
361
1
 * ```
362
1
 */
363
#[register_rule(("Minion", 4400))]
364
8229377
fn sumeq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
365
8229377
    match expr {
366
3196
        SumEq(_metadata, exprs, eq_to) => Ok(Reduction::pure(And(
367
3196
            Metadata::new(),
368
3196
            vec![
369
3196
                SumGeq(Metadata::new(), exprs.clone(), Box::from(*eq_to.clone())),
370
3196
                SumLeq(Metadata::new(), exprs.clone(), Box::from(*eq_to.clone())),
371
3196
            ],
372
3196
        ))),
373
8226181
        _ => Err(RuleNotApplicable),
374
    }
375
8229377
}
376

            
377
/**
378
* Convert a Lt to an Ineq:
379

            
380
* ```text
381
1
* a < b ~> a <= b -1 ~> ineq(a,b,-1)
382
1
* ```
383
1
*/
384
#[register_rule(("Minion", 4100))]
385
8229394
fn lt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
386
8229394
    match expr {
387
3111
        Lt(metadata, a, b) => Ok(Reduction::pure(Ineq(
388
3111
            metadata.clone_dirty(),
389
3111
            a.clone(),
390
3111
            b.clone(),
391
3111
            Box::new(Atomic(Metadata::new(), Literal(Int(-1)))),
392
3111
        ))),
393
8226283
        _ => Err(RuleNotApplicable),
394
    }
395
8229394
}
396

            
397
/**
398
* Convert a Gt to an Ineq:
399
*
400
* ```text
401
1
* a > b ~> b <= a -1 ~> ineq(b,a,-1)
402
1
* ```
403
1
*/
404
#[register_rule(("Minion", 4100))]
405
8229377
fn gt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
406
8229377
    match expr {
407
        Gt(metadata, a, b) => Ok(Reduction::pure(Ineq(
408
            metadata.clone_dirty(),
409
            b.clone(),
410
            a.clone(),
411
            Box::new(Atomic(Metadata::new(), Literal(Int(-1)))),
412
        ))),
413
8229377
        _ => Err(RuleNotApplicable),
414
    }
415
8229377
}
416

            
417
/**
418
* Convert a Geq to an Ineq:
419
*
420
* ```text
421
1
* a >= b ~> b <= a + 0 ~> ineq(b,a,0)
422
1
* ```
423
1
*/
424
#[register_rule(("Minion", 4100))]
425
8229377
fn geq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
426
8229377
    match expr {
427
272
        Geq(metadata, a, b) => Ok(Reduction::pure(Ineq(
428
272
            metadata.clone_dirty(),
429
272
            b.clone(),
430
272
            a.clone(),
431
272
            Box::new(Atomic(Metadata::new(), Literal(Int(0)))),
432
272
        ))),
433
8229105
        _ => Err(RuleNotApplicable),
434
    }
435
8229377
}
436

            
437
/**
438
* Convert a Leq to an Ineq:
439
*
440
* ```text
441
1
* a <= b ~> a <= b + 0 ~> ineq(a,b,0)
442
1
* ```
443
1
*/
444
#[register_rule(("Minion", 4100))]
445
8229377
fn leq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
446
8229377
    match expr {
447
357
        Leq(metadata, a, b) => Ok(Reduction::pure(Ineq(
448
357
            metadata.clone_dirty(),
449
357
            a.clone(),
450
357
            b.clone(),
451
357
            Box::new(Atomic(Metadata::new(), Literal(Int(0)))),
452
357
        ))),
453
8229020
        _ => Err(RuleNotApplicable),
454
    }
455
8229377
}
456

            
457
/// ```text
458
/// x <= y + k ~> ineq(x,y,k)
459
/// ```
460

            
461
#[register_rule(("Minion",4400))]
462
8229377
fn x_leq_y_plus_k_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
463
8229377
    let Leq(_, x, b) = expr else {
464
8229020
        return Err(RuleNotApplicable);
465
    };
466

            
467
357
    let x @ Atomic(_, Reference(_)) = *x.to_owned() else {
468
119
        return Err(RuleNotApplicable);
469
    };
470

            
471
238
    let Sum(_, c) = *b.to_owned() else {
472
221
        return Err(RuleNotApplicable);
473
    };
474

            
475
17
    let [ref y @ Atomic(_, Reference(_)), ref k @ Atomic(_, Literal(_))] = c[..] else {
476
        return Err(RuleNotApplicable);
477
    };
478

            
479
17
    Ok(Reduction::pure(Ineq(
480
17
        expr.get_meta().clone_dirty(),
481
17
        Box::new(x),
482
17
        Box::new(y.clone()),
483
17
        Box::new(k.clone()),
484
17
    )))
485
8229377
}
486

            
487
// #[register_rule(("Minion", 99))]
488
// fn eq_to_leq_geq(expr: &Expr, _: &Model) -> ApplicationResult {
489
//     match expr {
490
//         Eq(metadata, a, b) => {
491
//             return Ok(Reduction::pure(Expr::And(
492
//                 metadata.clone(),
493
//                 vec![
494
//                     Expr::Leq(metadata.clone(), a.clone(), b.clone()),
495
//                     Expr::Geq(metadata.clone(), a.clone(), b.clone()),
496
//                 ],
497
//             )));
498
//         }
499
//         _ => Err(RuleNotApplicable),
500
//     }
501
// }
502

            
503
/// Flattening rule that converts boolean variables to watched-literal constraints.
504
///
505
/// For some boolean variable x:
506
/// ```text
507
/// and([x,...]) ~> and([w-literal(x,1),..])
508
///  or([x,...]) ~>  or([w-literal(x,1),..])
509
///  not(x)      ~>  w-literal(x,0)
510
/// ```
511
///
512
/// ## Rationale
513
///
514
/// Minion's watched-and and watched-or constraints only takes other constraints as arguments.
515
///
516
/// This restates boolean variables as the equivalent constraint "SAT if x is true".
517
///
518
#[register_rule(("Minion", 4100))]
519
8229377
fn boolean_literal_to_wliteral(expr: &Expr, mdl: &Model) -> ApplicationResult {
520
    use Domain::BoolDomain;
521
8229377
    match expr {
522
429114
        Or(m, vec) => {
523
429114
            let mut changed = false;
524
429114
            let mut new_vec = Vec::new();
525
2095760
            for expr in vec {
526
1666646
                new_vec.push(match expr {
527
170
                    Atomic(m, Reference(name))
528
170
                        if mdl
529
170
                            .get_domain(name)
530
170
                            .is_some_and(|x| matches!(x, BoolDomain)) =>
531
                    {
532
170
                        changed = true;
533
170
                        WatchedLiteral(m.clone_dirty(), name.clone(), Bool(true))
534
                    }
535
1666476
                    e => e.clone(),
536
                });
537
            }
538

            
539
429114
            if !changed {
540
429012
                return Err(RuleNotApplicable);
541
102
            }
542
102

            
543
102
            Ok(Reduction::pure(Or(m.clone_dirty(), new_vec)))
544
        }
545
20842
        And(m, vec) => {
546
20842
            let mut changed = false;
547
20842
            let mut new_vec = Vec::new();
548
620891
            for expr in vec {
549
600049
                new_vec.push(match expr {
550
85
                    Atomic(m, Reference(name))
551
85
                        if mdl
552
85
                            .get_domain(name)
553
85
                            .is_some_and(|x| matches!(x, BoolDomain)) =>
554
                    {
555
85
                        changed = true;
556
85
                        WatchedLiteral(m.clone_dirty(), name.clone(), Bool(true))
557
                    }
558
599964
                    e => e.clone(),
559
                });
560
            }
561

            
562
20842
            if !changed {
563
20757
                return Err(RuleNotApplicable);
564
85
            }
565
85

            
566
85
            Ok(Reduction::pure(And(m.clone_dirty(), new_vec)))
567
        }
568

            
569
1020
        Not(m, expr) => {
570
1020
            if let Atomic(_, Reference(name)) = (**expr).clone() {
571
34
                if mdl
572
34
                    .get_domain(&name)
573
34
                    .is_some_and(|x| matches!(x, BoolDomain))
574
                {
575
34
                    return Ok(Reduction::pure(WatchedLiteral(
576
34
                        m.clone_dirty(),
577
34
                        name.clone(),
578
34
                        Bool(false),
579
34
                    )));
580
                }
581
986
            }
582
986
            Err(RuleNotApplicable)
583
        }
584
7778401
        _ => Err(RuleNotApplicable),
585
    }
586
8229377
}
587

            
588
/// Flattening rule for not(X) in Minion, where X is a constraint.
589
///
590
/// ```text
591
/// not(X) ~> reify(X,0)
592
/// ```
593
///
594
/// This rule has lower priority than boolean_literal_to_wliteral so that we can assume that the
595
/// nested expressions are constraints not variables.
596

            
597
#[register_rule(("Minion", 4090))]
598
8229377
fn not_constraint_to_reify(expr: &Expr, _: &Model) -> ApplicationResult {
599
8228391
    if !matches!(expr, Not(_,c) if !matches!(**c, Atomic(_,_))) {
600
8228391
        return Err(RuleNotApplicable);
601
986
    }
602

            
603
986
    let Not(m, e) = expr else {
604
        unreachable!();
605
    };
606

            
607
986
    extra_check! {
608
986
        if !is_flat(e) {
609
884
            return Err(RuleNotApplicable);
610
102
        }
611
    };
612

            
613
102
    Ok(Reduction::pure(Reify(
614
102
        m.clone(),
615
102
        e.clone(),
616
102
        Box::new(Atomic(Metadata::new(), Literal(Bool(false)))),
617
102
    )))
618
8229377
}