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::is_atom,
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
18
fn introduce_diveq(expr: &Expr, _: &Model) -> ApplicationResult {
32
18
    // div = val
33
18
    let val: Atom;
34
18
    let div: Expr;
35
18
    let meta: Metadata;
36
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
155
    let expr = expr.with_children(children);
156
    Ok(Reduction::new(expr, new_tops, model.variables))
157
18
}
158

            
159
#[register_rule(("Minion", 4400))]
160
18
fn flatten_vecop(expr: &Expr, model: &Model) -> ApplicationResult {
161
18
    if !matches!(expr, Expr::Sum(_, _)) {
162
18
        return Err(RuleNotApplicable);
163
    }
164

            
165
    let mut children = expr.children();
166

            
167
    let mut model = model.clone();
168
    let mut num_changed = 0;
169
    let mut new_tops: Vec<Expr> = vec![];
170

            
171
    for child in children.iter_mut() {
172
        if let Some(aux_var_info) = to_aux_var(child, &model) {
173
            model = aux_var_info.model();
174
            new_tops.push(aux_var_info.top_level_expr());
175
            *child = aux_var_info.as_expr();
176
            num_changed += 1;
177
        }
178
    }
179

            
180
    if num_changed == 0 {
181
        return Err(RuleNotApplicable);
182
    }
183

            
184
    let expr = expr.with_children(children);
185

            
186
    Ok(Reduction::new(expr, new_tops, model.variables))
187
18
}
188

            
189
#[register_rule(("Minion", 4400))]
190
18
fn flatten_eq(expr: &Expr, model: &Model) -> ApplicationResult {
191
18
    if !matches!(expr, Expr::Eq(_, _, _)) {
192
18
        return Err(RuleNotApplicable);
193
    }
194

            
195
    let mut children = expr.children();
196
    debug_assert_eq!(children.len(), 2);
197

            
198
    let mut model = model.clone();
199
    let mut num_changed = 0;
200
    let mut new_tops: Vec<Expr> = vec![];
201

            
202
    for child in children.iter_mut() {
203
        if let Some(aux_var_info) = to_aux_var(child, &model) {
204
            model = aux_var_info.model();
205
            new_tops.push(aux_var_info.top_level_expr());
206
            *child = aux_var_info.as_expr();
207
            num_changed += 1;
208
        }
209
    }
210

            
211
    // eq: both sides have to be non flat for the rule to be applicable!
212
    if num_changed != 2 {
213
        return Err(RuleNotApplicable);
214
    }
215

            
216
    let expr = expr.with_children(children);
217

            
218
    Ok(Reduction::new(expr, new_tops, model.variables))
219
18
}
220

            
221
fn is_nested_sum(exprs: &Vec<Expr>) -> bool {
222
    for e in exprs {
223
        if let Sum(_, _) = e {
224
            return true;
225
        }
226
    }
227
    false
228
}
229

            
230
/**
231
 * 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)
232
 */
233
fn sum_to_vector(expr: &Expr) -> Result<Vec<Expr>, ApplicationError> {
234
    match expr {
235
        Sum(_, exprs) => {
236
            if is_nested_sum(exprs) {
237
                Err(RuleNotApplicable)
238
            } else {
239
                Ok(exprs.clone())
240
            }
241
        }
242
        _ => Err(RuleNotApplicable),
243
    }
244
}
245

            
246
// /**
247
//  * Convert an Eq to a conjunction of Geq and Leq:
248
//  * ```text
249
//  * a = b => a >= b && a <= b
250
//  * ```
251
//  */
252
// #[register_rule(("Minion", 100))]
253
// fn eq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
254
//     match expr {
255
//         Expr::Eq(metadata, a, b) => Ok(Reduction::pure(Expr::And(
256
//             metadata.clone_dirty(),
257
//             vec![
258
//                 Expr::Geq(metadata.clone_dirty(), a.clone(), b.clone()),
259
//                 Expr::Leq(metadata.clone_dirty(), a.clone(), b.clone()),
260
//             ],
261
//         ))),
262
//         _ => Err(ApplicationError::RuleNotApplicable),
263
//     }
264
// }
265

            
266
/**
267
 * Convert a Geq to a SumGeq if the left hand side is a sum:
268
 * ```text
269
 * sum([a, b, c]) >= d => sum_geq([a, b, c], d)
270
 * ```
271
 */
272
#[register_rule(("Minion", 4400))]
273
18
fn flatten_sum_geq(expr: &Expr, _: &Model) -> ApplicationResult {
274
18
    match expr {
275
        Geq(metadata, a, b) => {
276
            let exprs = sum_to_vector(a)?;
277
            Ok(Reduction::pure(SumGeq(
278
                metadata.clone_dirty(),
279
                exprs,
280
                b.clone(),
281
            )))
282
        }
283
18
        _ => Err(RuleNotApplicable),
284
    }
285
18
}
286

            
287
/**
288
 * Convert a Leq to a SumLeq if the left hand side is a sum:
289
 * ```text
290
 * sum([a, b, c]) <= d => sum_leq([a, b, c], d)
291
 * ```
292
 */
293
#[register_rule(("Minion", 4400))]
294
18
fn sum_leq_to_sumleq(expr: &Expr, _: &Model) -> ApplicationResult {
295
18
    match expr {
296
        Leq(metadata, a, b) => {
297
            let exprs = sum_to_vector(a)?;
298
            Ok(Reduction::pure(SumLeq(
299
                metadata.clone_dirty(),
300
                exprs,
301
                b.clone(),
302
            )))
303
        }
304
18
        _ => Err(RuleNotApplicable),
305
    }
306
18
}
307

            
308
/**
309
 * Convert a 'Eq(Sum([...]))' to a SumEq
310
 * ```text
311
 * eq(sum([a, b]), c) => sumeq([a, b], c)
312
 * ```
313
*/
314
#[register_rule(("Minion", 4400))]
315
18
fn sum_eq_to_sumeq(expr: &Expr, _: &Model) -> ApplicationResult {
316
18
    match expr {
317
        Eq(metadata, a, b) => {
318
            if let Ok(exprs) = sum_to_vector(a) {
319
                Ok(Reduction::pure(SumEq(
320
                    metadata.clone_dirty(),
321
                    exprs,
322
                    b.clone(),
323
                )))
324
            } else if let Ok(exprs) = sum_to_vector(b) {
325
                Ok(Reduction::pure(SumEq(
326
                    metadata.clone_dirty(),
327
                    exprs,
328
                    a.clone(),
329
                )))
330
            } else {
331
                Err(RuleNotApplicable)
332
            }
333
        }
334
18
        _ => Err(RuleNotApplicable),
335
    }
336
18
}
337

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

            
367
/**
368
* Convert a Lt to an Ineq:
369

            
370
* ```text
371
* a < b ~> a <= b -1 ~> ineq(a,b,-1)
372
* ```
373
*/
374
#[register_rule(("Minion", 4100))]
375
18
fn lt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
376
18
    match expr {
377
        Lt(metadata, a, b) => Ok(Reduction::pure(Ineq(
378
            metadata.clone_dirty(),
379
            a.clone(),
380
            b.clone(),
381
            Box::new(Atomic(Metadata::new(), Literal(Int(-1)))),
382
        ))),
383
18
        _ => Err(RuleNotApplicable),
384
    }
385
18
}
386

            
387
/**
388
* Convert a Gt to an Ineq:
389
*
390
* ```text
391
* a > b ~> b <= a -1 ~> ineq(b,a,-1)
392
* ```
393
*/
394
#[register_rule(("Minion", 4100))]
395
18
fn gt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
396
18
    match expr {
397
        Gt(metadata, a, b) => Ok(Reduction::pure(Ineq(
398
            metadata.clone_dirty(),
399
            b.clone(),
400
            a.clone(),
401
            Box::new(Atomic(Metadata::new(), Literal(Int(-1)))),
402
        ))),
403
18
        _ => Err(RuleNotApplicable),
404
    }
405
18
}
406

            
407
/**
408
* Convert a Geq to an Ineq:
409
*
410
* ```text
411
* a >= b ~> b <= a + 0 ~> ineq(b,a,0)
412
* ```
413
*/
414
#[register_rule(("Minion", 4100))]
415
18
fn geq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
416
18
    match expr {
417
        Geq(metadata, a, b) => Ok(Reduction::pure(Ineq(
418
            metadata.clone_dirty(),
419
            b.clone(),
420
            a.clone(),
421
            Box::new(Atomic(Metadata::new(), Literal(Int(0)))),
422
        ))),
423
18
        _ => Err(RuleNotApplicable),
424
    }
425
18
}
426

            
427
/**
428
* Convert a Leq to an Ineq:
429
*
430
* ```text
431
* a <= b ~> a <= b + 0 ~> ineq(a,b,0)
432
* ```
433
*/
434
#[register_rule(("Minion", 4100))]
435
18
fn leq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
436
18
    match expr {
437
        Leq(metadata, a, b) => Ok(Reduction::pure(Ineq(
438
            metadata.clone_dirty(),
439
            a.clone(),
440
            b.clone(),
441
            Box::new(Atomic(Metadata::new(), Literal(Int(0)))),
442
        ))),
443
18
        _ => Err(RuleNotApplicable),
444
    }
445
18
}
446

            
447
/// ```text
448
/// x <= y + k ~> ineq(x,y,k)
449
/// ```
450

            
451
#[register_rule(("Minion",4400))]
452
18
fn x_leq_y_plus_k_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
453
18
    let Leq(_, x, b) = expr else {
454
18
        return Err(RuleNotApplicable);
455
    };
456

            
457
    let x @ Atomic(_, Reference(_)) = *x.to_owned() else {
458
        return Err(RuleNotApplicable);
459
    };
460

            
461
    let Sum(_, c) = *b.to_owned() else {
462
        return Err(RuleNotApplicable);
463
    };
464

            
465
    let [ref y @ Atomic(_, Reference(_)), ref k @ Atomic(_, Literal(_))] = c[..] else {
466
        return Err(RuleNotApplicable);
467
    };
468

            
469
    Ok(Reduction::pure(Ineq(
470
        expr.get_meta().clone_dirty(),
471
        Box::new(x),
472
        Box::new(y.clone()),
473
        Box::new(k.clone()),
474
    )))
475
18
}
476

            
477
// #[register_rule(("Minion", 99))]
478
// fn eq_to_leq_geq(expr: &Expr, _: &Model) -> ApplicationResult {
479
//     match expr {
480
//         Eq(metadata, a, b) => {
481
//             return Ok(Reduction::pure(Expr::And(
482
//                 metadata.clone(),
483
//                 vec![
484
//                     Expr::Leq(metadata.clone(), a.clone(), b.clone()),
485
//                     Expr::Geq(metadata.clone(), a.clone(), b.clone()),
486
//                 ],
487
//             )));
488
//         }
489
//         _ => Err(RuleNotApplicable),
490
//     }
491
// }
492

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

            
529
9
            if !changed {
530
9
                return Err(RuleNotApplicable);
531
            }
532

            
533
            Ok(Reduction::pure(Or(m.clone_dirty(), new_vec)))
534
        }
535
        And(m, vec) => {
536
            let mut changed = false;
537
            let mut new_vec = Vec::new();
538
            for expr in vec {
539
                new_vec.push(match expr {
540
                    Atomic(m, Reference(name))
541
                        if mdl
542
                            .get_domain(name)
543
                            .is_some_and(|x| matches!(x, BoolDomain)) =>
544
                    {
545
                        changed = true;
546
                        WatchedLiteral(m.clone_dirty(), name.clone(), Bool(true))
547
                    }
548
                    e => e.clone(),
549
                });
550
            }
551

            
552
            if !changed {
553
                return Err(RuleNotApplicable);
554
            }
555

            
556
            Ok(Reduction::pure(And(m.clone_dirty(), new_vec)))
557
        }
558

            
559
        Not(m, expr) => {
560
            if let Atomic(_, Reference(name)) = (**expr).clone() {
561
                if mdl
562
                    .get_domain(&name)
563
                    .is_some_and(|x| matches!(x, BoolDomain))
564
                {
565
                    return Ok(Reduction::pure(WatchedLiteral(
566
                        m.clone_dirty(),
567
                        name.clone(),
568
                        Bool(false),
569
                    )));
570
                }
571
            }
572
            Err(RuleNotApplicable)
573
        }
574
9
        _ => Err(RuleNotApplicable),
575
    }
576
18
}
577

            
578
/// Flattening rule for not(X) in Minion, where X is a constraint.
579
///
580
/// ```text
581
/// not(X) ~> reify(X,0)
582
/// ```
583
///
584
/// This rule has lower priority than boolean_literal_to_wliteral so that we can assume that the
585
/// nested expressions are constraints not variables.
586

            
587
#[register_rule(("Minion", 4090))]
588
18
fn not_constraint_to_reify(expr: &Expr, _: &Model) -> ApplicationResult {
589
18
    if !matches!(expr, Not(_,c) if !matches!(**c, Atomic(_,_))) {
590
18
        return Err(RuleNotApplicable);
591
    }
592

            
593
    let Not(m, e) = expr else {
594
        unreachable!();
595
    };
596

            
597
    extra_check! {
598
        if !is_flat(e) {
599
            return Err(RuleNotApplicable);
600
        }
601
    };
602

            
603
    Ok(Reduction::pure(Reify(
604
        m.clone(),
605
        e.clone(),
606
        Box::new(Atomic(Metadata::new(), Literal(Bool(false)))),
607
    )))
608
18
}