1
//! Channelling between the SAT integer encodings.
2
//!
3
//! Which encoding an integer gets is a per-declaration representation choice, so one constraint
4
//! can end up comparing a one-hot variable with a bit-vector one. The encoding-specific rules all
5
//! decline in that case -- each only knows how to read its own layout -- so a mixed operation is
6
//! first rewritten to put every operand in the same encoding.
7
//!
8
//! Fallback operations use actual-value binary circuits. Direct and Order supply value
9
//! indicators; Offset adds the domain minimum and Rank maps canonical intervals to their
10
//! numeric values. These temporary circuit operands do not create additional representations.
11

            
12
use std::collections::{HashSet, VecDeque};
13

            
14
use conjure_cp::ast::{
15
    AbstractLiteral, Atom, DomainPtr, Expression as Expr, Literal, Metadata, Moo, SATIntEncoding,
16
    SatEncodingDecision, SymbolTable,
17
};
18
use conjure_cp::into_matrix_expr;
19
use conjure_cp::rule_engine::{
20
    ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect, register_rule,
21
};
22

            
23
use crate::backends::sat::boolean::{
24
    tseytin_and, tseytin_mux, tseytin_not, tseytin_or, tseytin_xor,
25
};
26
use crate::backends::sat::int::log::bit_magnitude;
27
use uniplate::Uniplate;
28

            
29
/// Put every `SATInt` operand of an operation into the logarithmic encoding.
30
///
31
/// This is a fallback, and sits below every encoding-specific operation rule on purpose. An
32
/// operation whose operands share an encoding that knows how to encode it is handled there and
33
/// never reaches this rule. Remaining operands may have different encodings, use an encoding with
34
/// no rule for the operation, or include Boolean indicators needing an actual-value circuit view.
35
#[register_rule("SAT", 4000, [Eq, Neq, Lt, Gt, Leq, Geq, AllDiff, AllDifferentExcept, Table, NegativeTable, SatElement, SatObjective, Sum, Product, Min, Max, Abs, Neg, SafeDiv, SafeMod, SafePow])]
36
767785
fn unify_sat_int_encodings(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
37
767785
    let mut encodings = HashSet::new();
38
767785
    let mut has_indicator = false;
39
767785
    let mut operands_ready = true;
40
767785
    let table = matches!(expr, Expr::Table(..) | Expr::NegativeTable(..));
41
767785
    let inputs = if table {
42
8330
        expr.children()
43
8330
            .into_iter()
44
16660
            .flat_map(|child| {
45
16660
                crate::shared::utils::table_operand(&child)
46
16660
                    .universe()
47
16660
                    .into_iter()
48
204120
                    .filter(|input| matches!(input, Expr::SATInt(..)))
49
                    .collect::<Vec<_>>()
50
16660
            })
51
            .collect::<Vec<_>>()
52
    } else {
53
759455
        operands(expr).collect()
54
    };
55
767785
    for operand in inputs {
56
1763042
        let indicator = ready_indicator(&operand);
57
1763042
        has_indicator |= indicator;
58
1763042
        operands_ready &= indicator
59
1717262
            || matches!(
60
1717262
                operand,
61
                Expr::SATInt(..) | Expr::Atomic(_, Atom::Literal(Literal::Int(_)))
62
            );
63
1763042
        if let Expr::SATInt(_, encoding, _, _) = operand {
64
689550
            encodings.insert(encoding);
65
1073492
        }
66
    }
67

            
68
    // Semantic allDifferent/table/element operands retain their ready value views.
69
    // Sparse rank still needs interval decoding; other numeric views are native.
70
767785
    let retains_views = matches!(
71
767785
        expr,
72
        Expr::AllDiff(..)
73
            | Expr::AllDifferentExcept(..)
74
            | Expr::Table(..)
75
            | Expr::NegativeTable(..)
76
            | Expr::SatElement(..)
77
            | Expr::SatObjective(..)
78
    );
79
767785
    if retains_views && !encodings.iter().any(needs_actual_value_circuit) {
80
32431
        return Err(RuleNotApplicable);
81
735354
    }
82
    // Ready indicators need an actual-value view only when a circuit is required.
83
    // Linear and count decisions run earlier and retain their original inputs.
84
735354
    let has_indicator = !retains_views && has_indicator && operands_ready;
85
735354
    if !has_indicator
86
722467
        && encodings
87
722467
            .iter()
88
320352
            .all(|encoding| matches!(encoding, SATIntEncoding::Log))
89
    {
90
515837
        return Err(RuleNotApplicable);
91
219517
    }
92

            
93
219517
    let mut decisions = Vec::new();
94
219517
    let mut new_symbols = symbols.clone();
95

            
96
485017
    let mut convert = |input: Expr| {
97
485017
        if (retains_views
98
6314
            && !matches!(&input, Expr::SATInt(_, encoding, _, _) if needs_actual_value_circuit(encoding)))
99
483379
            || (!has_indicator && ready_indicator(&input))
100
        {
101
1638
            input
102
        } else {
103
483379
            to_log(input, &mut decisions, &mut new_symbols)
104
        }
105
485017
    };
106
219517
    let children: VecDeque<Expr> = expr
107
219517
        .children()
108
219517
        .into_iter()
109
394388
        .map(|child| {
110
394388
            if table {
111
1680
                map_table_cells(crate::shared::utils::table_operand(&child), &mut convert)
112
            } else {
113
392708
                match matrix_child(&child) {
114
                    Some((elements, index_domain)) => {
115
43729
                        let converted: Vec<Expr> = elements.into_iter().map(&mut convert).collect();
116
43729
                        rebuild_matrix_child(converted, index_domain)
117
                    }
118
348979
                    None => convert(child),
119
                }
120
            }
121
394388
        })
122
        .collect();
123

            
124
219517
    let new_expression = expr.with_children(children);
125
219517
    if new_expression == *expr && decisions.is_empty() {
126
        return Err(RuleNotApplicable);
127
219517
    }
128
219517
    Ok(RuleEffect::sat(new_expression, decisions, new_symbols))
129
767785
}
130

            
131
/// Whether semantic numeric decisions need a decoded actual-value circuit.
132
24675
fn needs_actual_value_circuit(encoding: &SATIntEncoding) -> bool {
133
24675
    matches!(encoding, SATIntEncoding::Rank(ranges) if ranges.len() != 1)
134
24675
}
135

            
136
5460
fn map_table_cells(expression: Expr, convert: &mut impl FnMut(Expr) -> Expr) -> Expr {
137
5460
    if matches!(expression, Expr::SATInt(..)) {
138
2940
        return convert(expression);
139
2520
    }
140
    // Cells can contain packed-sequence arithmetic as well as collection wrappers.
141
2520
    let children = expression
142
2520
        .children()
143
2520
        .into_iter()
144
3780
        .map(|child| map_table_cells(crate::shared::utils::table_operand(&child), convert))
145
        .collect();
146
2520
    expression.with_children(children)
147
5460
}
148

            
149
/// The elements of a matrix-literal child, with the index domain to rebuild it under.
150
///
151
/// `unwrap_list` only recognises the implied `int(1..)` index domain, but a matrix rebuilt from its
152
/// components keeps the one it was declared with. These rules look through a child to reach the
153
/// operands underneath, and an operand is no less an operand for being indexed from zero.
154
15702468
fn matrix_child(expr: &Expr) -> Option<(Vec<Expr>, DomainPtr)> {
155
15702468
    match expr {
156
        Expr::AbstractLiteral(_, AbstractLiteral::Matrix(elements, index_domain)) => {
157
1719032
            Some((elements.clone(), index_domain.clone()))
158
        }
159
13983436
        _ => None,
160
    }
161
15702468
}
162

            
163
/// Rebuild a matrix-literal child from new elements, keeping its index domain.
164
56770
fn rebuild_matrix_child(elements: Vec<Expr>, index_domain: DomainPtr) -> Expr {
165
56770
    Expr::AbstractLiteral(
166
56770
        Metadata::new(),
167
56770
        AbstractLiteral::Matrix(elements, index_domain),
168
56770
    )
169
56770
}
170

            
171
/// The operand expressions of `expr`, looking one level into list children.
172
///
173
/// `Sum` and friends hold their operands in a matrix child rather than directly, so a mixed
174
/// summation would otherwise look uniform from the outside.
175
8205944
fn operands(expr: &Expr) -> impl Iterator<Item = Expr> {
176
8205944
    expr.children()
177
8205944
        .into_iter()
178
14695383
        .flat_map(|child| match matrix_child(&child) {
179
1662262
            Some((elements, _)) => elements,
180
13033121
            None => vec![child],
181
14695383
        })
182
8205944
}
183

            
184
/// Re-encode one operand into the logarithmic encoding, leaving anything else alone.
185
483379
fn to_log(expr: Expr, decisions: &mut Vec<SatEncodingDecision>, symbols: &mut SymbolTable) -> Expr {
186
483379
    if ready_indicator(&expr) {
187
31570
        let Expr::ToInt(_, input) = expr else {
188
            unreachable!()
189
        };
190
31570
        return Expr::SATInt(
191
31570
            Metadata::new(),
192
31570
            SATIntEncoding::Log,
193
31570
            Moo::new(into_matrix_expr!(vec![
194
31570
                input.as_ref().clone(),
195
31570
                false.into()
196
31570
            ])),
197
31570
            (0, 1),
198
31570
        );
199
451809
    }
200
451809
    let Expr::SATInt(_, encoding, bits, bounds) = &expr else {
201
15834
        return expr;
202
    };
203
435975
    if matches!(encoding, SATIntEncoding::Log) {
204
770
        return expr;
205
435205
    }
206
435205
    let Some(bits) = bits.as_ref().clone().into_list() else {
207
        return expr;
208
    };
209

            
210
435205
    let (low, high) = *bounds;
211
435205
    let width = bit_magnitude(low).max(bit_magnitude(high));
212
435205
    if matches!(encoding, SATIntEncoding::Offset | SATIntEncoding::Rank(_)) {
213
99870
        let mut decoded = add_unsigned_constant(&bits, i64::from(low), width, decisions, symbols);
214
99870
        if let SATIntEncoding::Rank(ranges) = encoding {
215
63862
            let mut start = 0u64;
216
63862
            for (index, &(range_low, range_high)) in ranges.iter().enumerate() {
217
99272
                if index > 0 {
218
35410
                    let selector = unsigned_at_least(&bits, start, decisions, symbols);
219
35410
                    let candidate = add_unsigned_constant(
220
35410
                        &bits,
221
35410
                        i64::from(range_low) - start as i64,
222
35410
                        width,
223
35410
                        decisions,
224
35410
                        symbols,
225
35410
                    );
226
35410
                    decoded = decoded
227
35410
                        .into_iter()
228
35410
                        .zip(candidate)
229
131003
                        .map(|(old, new)| {
230
131003
                            tseytin_mux(selector.clone(), old, new, decisions, symbols)
231
131003
                        })
232
                        .collect();
233
63862
                }
234
99272
                start += (i64::from(range_high) - i64::from(range_low) + 1) as u64;
235
            }
236
36008
        }
237
99870
        return Expr::SATInt(
238
99870
            Metadata::new(),
239
99870
            SATIntEncoding::Log,
240
99870
            Moo::new(into_matrix_expr!(decoded)),
241
99870
            (low, high),
242
99870
        );
243
335335
    }
244

            
245
335335
    if matches!(encoding, SATIntEncoding::SignMagnitude) {
246
35665
        return Expr::SATInt(
247
35665
            Metadata::new(),
248
35665
            SATIntEncoding::Log,
249
35665
            Moo::new(into_matrix_expr!(sign_magnitude_to_twos_complement(
250
35665
                &bits, width, decisions, symbols
251
35665
            ))),
252
35665
            (low, high),
253
35665
        );
254
299670
    }
255

            
256
    // Both remaining encodings lay out one bit per value; order does so cumulatively, so take the
257
    // difference between neighbouring thresholds to recover "x is exactly this value".
258
299670
    let value_bits = match encoding {
259
263970
        SATIntEncoding::Direct => bits,
260
35700
        SATIntEncoding::Order => order_to_value_bits(&bits, decisions, symbols),
261
        SATIntEncoding::Log
262
        | SATIntEncoding::Offset
263
        | SATIntEncoding::Rank(_)
264
        | SATIntEncoding::SignMagnitude => return expr,
265
    };
266

            
267
299670
    let log_bits: Vec<Expr> = (0..width)
268
954660
        .map(|index| {
269
            // Bit `index` of `x` is set exactly when `x` takes one of the values whose two's
270
            // complement has that bit set.
271
954660
            let terms: Vec<Expr> = value_bits
272
954660
                .iter()
273
954660
                .enumerate()
274
5324410
                .filter(|(offset, _)| ((low + *offset as i32) as u32) >> index & 1 == 1)
275
1988770
                .map(|(_, bit)| bit.clone())
276
                .collect();
277
954660
            match terms.len() {
278
387975
                0 => Expr::Atomic(Metadata::new(), Atom::Literal(Literal::Bool(false))),
279
223230
                1 => terms.into_iter().next().expect("just checked the length"),
280
343455
                _ => tseytin_or(&terms, decisions, symbols),
281
            }
282
954660
        })
283
        .collect();
284

            
285
299670
    Expr::SATInt(
286
299670
        Metadata::new(),
287
299670
        SATIntEncoding::Log,
288
299670
        Moo::new(into_matrix_expr!(log_bits)),
289
299670
        (low, high),
290
299670
    )
291
483379
}
292

            
293
2690390
fn ready_indicator(expr: &Expr) -> bool {
294
2690390
    matches!(expr, Expr::ToInt(_, input)
295
83909
        if crate::shared::utils::is_literal(input)
296
77350
            && input.domain_of().is_some_and(|domain| domain.is_bool()))
297
2690390
}
298

            
299
/// Turn order-encoded thresholds into one bit per value.
300
///
301
/// `x = low + i` exactly when `x >= low + i` holds and `x >= low + i + 1` does not; past the top
302
/// threshold there is nothing left to exclude.
303
35700
fn order_to_value_bits(
304
35700
    thresholds: &[Expr],
305
35700
    decisions: &mut Vec<SatEncodingDecision>,
306
35700
    symbols: &mut SymbolTable,
307
35700
) -> Vec<Expr> {
308
35700
    thresholds
309
35700
        .iter()
310
35700
        .enumerate()
311
211505
        .map(|(index, threshold)| match thresholds.get(index + 1) {
312
            Some(next) => {
313
175805
                let not_next = tseytin_not(next.clone(), decisions, symbols);
314
175805
                tseytin_and(&[threshold.clone(), not_next], decisions, symbols)
315
            }
316
35700
            None => threshold.clone(),
317
211505
        })
318
        .collect()
319
35700
}
320

            
321
/// A constant in a given SAT integer encoding, with its value range pinned to the constant.
322
///
323
/// The direct and order encodings both need only a single set bit here: the padding in their
324
/// operand validators widens it to the range the operation works over.
325
313709
pub(super) fn sat_int_literal(encoding: &SATIntEncoding, value: i32) -> Expr {
326
313709
    let bits = match encoding {
327
59465
        SATIntEncoding::Log => log_literal_bits(value),
328
32799
        SATIntEncoding::Offset | SATIntEncoding::Rank(_) => vec![false.into()],
329
12600
        SATIntEncoding::SignMagnitude => sign_magnitude_literal_bits(value),
330
        SATIntEncoding::Direct | SATIntEncoding::Order => {
331
208845
            vec![Expr::Atomic(
332
208845
                Metadata::new(),
333
208845
                Atom::Literal(Literal::Bool(true)),
334
208845
            )]
335
        }
336
    };
337

            
338
    Expr::SATInt(
339
313709
        Metadata::new(),
340
313709
        match encoding {
341
20143
            SATIntEncoding::Rank(_) => SATIntEncoding::Rank(vec![(value, value)]),
342
293566
            other => other.clone(),
343
        },
344
313709
        Moo::new(into_matrix_expr!(bits)),
345
313709
        (value, value),
346
    )
347
313709
}
348

            
349
/// The sign-and-magnitude bits of a constant: its magnitude, least significant first, then the sign.
350
12600
fn sign_magnitude_literal_bits(value: i32) -> Vec<Expr> {
351
12600
    let magnitude = i64::from(value).unsigned_abs();
352
12600
    let width = (64 - magnitude.leading_zeros()).max(1);
353
38535
    let bit = |set: bool| Expr::Atomic(Metadata::new(), Atom::Literal(Literal::Bool(set)));
354
12600
    (0..width)
355
25935
        .map(|index| bit((magnitude >> index) & 1 == 1))
356
12600
        .chain(std::iter::once(bit(value < 0)))
357
        .collect()
358
12600
}
359

            
360
/// Two's complement of a sign-and-magnitude code, in `width` bits.
361
///
362
/// The magnitude is inverted and incremented when the sign is set: with the sign as the carry in,
363
/// bit `i` is `(m_i xor s) xor carry_i` and the carry on is `(m_i xor s) and carry_i`. Bits past
364
/// the magnitude's are zero, so they are just the sign.
365
35665
fn sign_magnitude_to_twos_complement(
366
35665
    bits: &[Expr],
367
35665
    width: usize,
368
35665
    decisions: &mut Vec<SatEncodingDecision>,
369
35665
    symbols: &mut SymbolTable,
370
35665
) -> Vec<Expr> {
371
35665
    let (sign, magnitude) = bits.split_last().expect("a sign bit");
372
35665
    let mut carry = sign.clone();
373
35665
    let mut out = Vec::with_capacity(width);
374
35665
    for index in 0..width {
375
123235
        let flipped = match magnitude.get(index) {
376
87850
            Some(bit) => tseytin_xor(bit.clone(), sign.clone(), decisions, symbols),
377
35385
            None => sign.clone(),
378
        };
379
123235
        out.push(tseytin_xor(
380
123235
            flipped.clone(),
381
123235
            carry.clone(),
382
123235
            decisions,
383
123235
            symbols,
384
123235
        ));
385
123235
        carry = tseytin_and(&[flipped, carry], decisions, symbols);
386
    }
387
35665
    out
388
35665
}
389

            
390
/// The two's-complement bits of a constant, least significant first.
391
59465
fn log_literal_bits(value: i32) -> Vec<Expr> {
392
59465
    let mut remaining = value as u32;
393
59465
    (0..bit_magnitude(value))
394
178815
        .map(|_| {
395
178815
            let bit = Expr::Atomic(
396
178815
                Metadata::new(),
397
178815
                Atom::Literal(Literal::Bool(remaining & 1 != 0)),
398
178815
            );
399
178815
            remaining >>= 1;
400
178815
            bit
401
178815
        })
402
        .collect()
403
59465
}
404

            
405
/// Encode integer constants into whichever encoding the operation's variables are in.
406
///
407
/// Constants have no representation of their own to follow, and encoding one eagerly would mean
408
/// guessing: the same `2` belongs in a bit vector next to a log-encoded variable and in a one-hot
409
/// vector next to a direct-encoded one. Deferring until the operation is known keeps the encodings
410
/// separate, and leaves every operation rule below with the invariant it relies on -- that its
411
/// integer operands are all `SATInt`s.
412
#[register_rule("SAT", 9400, [Eq, Neq, Lt, Gt, Leq, Geq, Sum, Product, Min, Max, SafeDiv, SafeMod, SafePow])]
413
7446489
fn encode_sat_int_literals(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
414
7446489
    let mut encoding = None;
415
7446489
    let mut has_literal = false;
416
7446489
    for operand in operands(expr) {
417
20250869
        match operand {
418
5048356
            Expr::SATInt(_, found, _, _) => encoding = encoding.or(Some(found)),
419
3697670
            Expr::Atomic(_, Atom::Literal(Literal::Int(_))) => has_literal = true,
420
11504843
            _ => {}
421
        }
422
    }
423

            
424
    // Without a variable to follow there is nothing to match, and without a constant there is
425
    // nothing to do.
426
7446489
    let (Some(encoding), true) = (encoding, has_literal) else {
427
7132780
        return Err(RuleNotApplicable);
428
    };
429

            
430
628937
    let encode = |expr: Expr| match expr {
431
313709
        Expr::Atomic(_, Atom::Literal(Literal::Int(value))) => sat_int_literal(&encoding, value),
432
315228
        other => other,
433
628937
    };
434

            
435
313709
    let children: VecDeque<Expr> = expr
436
313709
        .children()
437
313709
        .into_iter()
438
614377
        .map(|child| match matrix_child(&child) {
439
            Some((elements, index_domain)) => {
440
13041
                rebuild_matrix_child(elements.into_iter().map(encode).collect(), index_domain)
441
            }
442
601336
            None => encode(child),
443
614377
        })
444
        .collect();
445

            
446
313709
    Ok(RuleEffect::pure(expr.with_children(children)))
447
7446489
}
448

            
449
/// Add a constant modulo the semantic width; structural constraints exclude unused codes.
450
135280
fn add_unsigned_constant(
451
135280
    bits: &[Expr],
452
135280
    constant: i64,
453
135280
    width: usize,
454
135280
    decisions: &mut Vec<SatEncodingDecision>,
455
135280
    symbols: &mut SymbolTable,
456
135280
) -> Vec<Expr> {
457
135280
    let mut carry: Expr = false.into();
458
135280
    let mut out = Vec::with_capacity(width);
459
135280
    for index in 0..width {
460
476204
        let bit = bits.get(index).cloned().unwrap_or_else(|| false.into());
461
476204
        let sum = tseytin_xor(bit.clone(), carry.clone(), decisions, symbols);
462
476204
        if (constant as u32 >> index) & 1 == 1 {
463
162081
            out.push(tseytin_not(sum, decisions, symbols));
464
162081
            carry = tseytin_or(&[bit, carry], decisions, symbols);
465
        } else {
466
314123
            out.push(sum);
467
314123
            carry = tseytin_and(&[bit, carry], decisions, symbols);
468
        }
469
    }
470
135280
    out
471
135280
}
472
/// Compare an unsigned code with an interval's starting rank.
473
35410
fn unsigned_at_least(
474
35410
    bits: &[Expr],
475
35410
    minimum: u64,
476
35410
    decisions: &mut Vec<SatEncodingDecision>,
477
35410
    symbols: &mut SymbolTable,
478
35410
) -> Expr {
479
35410
    let maximum = minimum - 1;
480
35410
    let mut leq: Expr = true.into();
481
35410
    for (index, bit) in bits.iter().enumerate() {
482
68807
        let not = tseytin_not(bit.clone(), decisions, symbols);
483
68807
        leq = if (maximum >> index) & 1 == 1 {
484
17451
            tseytin_or(&[not, leq], decisions, symbols)
485
        } else {
486
51356
            tseytin_and(&[not, leq], decisions, symbols)
487
        };
488
    }
489
35410
    tseytin_not(leq, decisions, symbols)
490
35410
}