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
        Domain,
14
        Expression::{self as Expr, *},
15
        Literal::*,
16
    },
17
    bug,
18
    rules::utils::exprs_to_conjunction,
19
};
20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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