1
use conjure_cp::essence_expr;
2
use conjure_cp::{
3
    ast::Metadata,
4
    ast::{Atom, Expression as Expr, Moo, SymbolTable},
5
    into_matrix_expr,
6
    rule_engine::{
7
        ApplicationError, ApplicationResult, RuleEffect, register_rule, register_rule_set,
8
    },
9
};
10

            
11
use ApplicationError::RuleNotApplicable;
12

            
13
register_rule_set!("Base", ());
14

            
15
/**
16
 * Turn a Min into a new variable and post a top-level constraint to ensure the new variable is the minimum.
17
 * ```text
18
 * min([a, b]) ~> c ; c <= a & c <= b & (c = a | c = b)
19
 * ```
20
 */
21
#[register_rule("Base", 6000, [Min])]
22
15044
fn min_to_var(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
23
15044
    let Expr::Min(_, inside_min_expr) = expr else {
24
3704
        return Err(RuleNotApplicable);
25
    };
26

            
27
11340
    let Some(exprs) = inside_min_expr.as_ref().clone().into_list() else {
28
11186
        return Err(RuleNotApplicable);
29
    };
30

            
31
154
    let domain = expr.domain_of().ok_or(ApplicationError::DomainError)?;
32
154
    let mut symbols = symbols.clone();
33

            
34
154
    let atom_inner = Atom::new_ref(symbols.gen_find_auxiliary(&domain));
35
154
    let atom_expr = Expr::Atomic(Metadata::new(), atom_inner);
36

            
37
154
    let mut new_top = Vec::new();
38
154
    let mut disjunction = Vec::new();
39
154
    for e in exprs {
40
        // Use the Expr::Atomic version in constraints
41
322
        new_top.push(essence_expr!(&atom_expr <= &e));
42
322
        disjunction.push(essence_expr!(&atom_expr = &e));
43
    }
44
    // TODO: deal with explicit index domains
45
154
    new_top.push(Expr::Or(
46
154
        Metadata::new(),
47
154
        Moo::new(into_matrix_expr![disjunction]),
48
154
    ));
49

            
50
154
    Ok(RuleEffect::new(
51
        // Return the Expr::Atomic
52
154
        essence_expr!(&atom_expr),
53
154
        new_top,
54
154
        symbols.clone(),
55
154
    ))
56
15044
}
57

            
58
/**
59
 * Turn a Max into a new variable and post a top level constraint to ensure the new variable is the maximum.
60
 * ```text
61
 * max([a, b]) ~> c ; c >= a & c >= b & (c = a | c = b)
62
 * ```
63
 */
64
#[register_rule("Base", 6000, [Max])]
65
13588
fn max_to_var(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
66
13588
    let Expr::Max(_, inside_max_expr) = expr else {
67
3704
        return Err(RuleNotApplicable);
68
    };
69

            
70
9884
    let Some(exprs) = inside_max_expr.as_ref().clone().into_list() else {
71
9695
        return Err(RuleNotApplicable);
72
    };
73

            
74
189
    let domain = expr.domain_of().ok_or(ApplicationError::DomainError)?;
75
189
    let mut symbols: SymbolTable = symbols.clone();
76

            
77
189
    let atom_inner = Atom::new_ref(symbols.gen_find_auxiliary(&domain));
78
189
    let atom_expr = Expr::Atomic(Metadata::new(), atom_inner);
79

            
80
189
    let mut new_top = Vec::new(); // the new variable must be more than or equal to all the other variables
81
189
    let mut disjunction = Vec::new(); // the new variable must more than or equal to one of the variables
82
189
    for e in exprs {
83
399
        new_top.push(essence_expr!(&atom_expr >= &e));
84
399
        disjunction.push(essence_expr!(&atom_expr = &e));
85
    }
86
    // FIXME: deal with explicitly given domains
87
189
    new_top.push(Expr::Or(
88
189
        Metadata::new(),
89
189
        Moo::new(into_matrix_expr![disjunction]),
90
189
    ));
91

            
92
189
    Ok(RuleEffect::new(essence_expr!(&atom_expr), new_top, symbols))
93
13588
}