1
//! Semantic SAT encoding decisions. Clauses and solver literals belong to the adaptor.
2
use crate::ast::Expression;
3
use serde::{Deserialize, Serialize};
4
use std::fmt::{Display, Formatter};
5
use uniplate::Uniplate;
6

            
7
// One listing defines each family's labels, parsing and heuristic iteration order.
8
macro_rules! encoding_labels {
9
    ($type:ident, $family:literal, $len:literal, [$($variant:ident => $label:literal),+ $(,)?]) => {
10
        impl $type {
11
            /// Algorithms in heuristic and command-line order.
12
            pub const ALL: [Self; $len] = [$(Self::$variant),+];
13
            /// Canonical command-line labels in the same order as `ALL`.
14
            pub const LABELS: [&'static str; $len] = [$($label),+];
15
        }
16
        impl Display for $type {
17
13588733
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
18
13588733
                f.write_str(match self { $(Self::$variant => $label),+ })
19
13588733
            }
20
        }
21
        impl std::str::FromStr for $type {
22
            type Err = String;
23
56
            fn from_str(value: &str) -> Result<Self, String> {
24
56
                match value {
25
8
                    $($label => Ok(Self::$variant),)+
26
                    _ => Err(format!(concat!("unknown ", $family, " '{}'; expected {}"), value, Self::LABELS.join(", "))),
27
                }
28
56
            }
29
        }
30
    };
31
}
32

            
33
/// A Boolean operation whose truth is asserted or bound to a named output.
34
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
35
#[biplate(to=Expression)]
36
#[biplate(to=crate::ast::DeclarationPtr)]
37
pub enum SatEncodingDecision {
38
    /// Assert the semantic Boolean expression; the adaptor chooses clauses and witnesses.
39
    Assert(Expression),
40
    /// At most one input may be true; the selected library algorithm is recorded explicitly.
41
    AtMostOne {
42
        inputs: Vec<Expression>,
43
        encoding: Option<EncodingSelection<AmoEncoding>>,
44
    },
45
    /// Compare the number of true operands with a mathematical integer bound.
46
    Cardinality {
47
        inputs: Vec<Expression>,
48
        relation: CardinalityRelation,
49
        bound: i64,
50
        encoding: Option<EncodingSelection<CardinalityEncoding>>,
51
    },
52
    /// Define the truth of a Boolean count comparison using cardinality and AMO providers.
53
    CountRelation {
54
        output: Expression,
55
        inputs: Vec<Expression>,
56
        relation: IntegerRelation,
57
        bound: i64,
58
        encoding: Option<EncodingSelection<CardinalityEncoding>>,
59
        /// Selected when either implication includes an at-most-one threshold.
60
        amo_encoding: Option<EncodingSelection<AmoEncoding>>,
61
    },
62
    /// Compare a signed weighted Boolean sum with an integer bound.
63
    PseudoBoolean {
64
        terms: Vec<(i64, Expression)>,
65
        /// Relationships already guaranteed by the integer representation constraints.
66
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
67
        groups: Vec<PbTermGroup>,
68
        relation: CardinalityRelation,
69
        bound: i64,
70
        encoding: Option<EncodingSelection<PbEncoding>>,
71
    },
72
    /// Define an integer relation over a signed weighted view of the numeric operands.
73
    IntegerRelation {
74
        output: Expression,
75
        terms: Vec<(i64, Expression)>,
76
        groups: Vec<PbTermGroup>,
77
        relation: IntegerRelation,
78
        bound: i64,
79
        encoding: Option<EncodingSelection<PbEncoding>>,
80
    },
81
    /// Minimise or maximise an actual-value integer view using the selected PB provider.
82
    Objective {
83
        minimise: bool,
84
        value: SatIntegerView,
85
        encoding: Option<EncodingSelection<PbEncoding>>,
86
    },
87
    /// Define scalar or whole-value distinctness, with an optional repeated exception value.
88
    AllDifferent {
89
        output: Expression,
90
        inputs: Vec<SatIntegerView>,
91
        /// Pair conditions for compound values, lowered through the existing equality rules.
92
        #[serde(default, skip_serializing_if = "Option::is_none")]
93
        comparisons: Option<Vec<Expression>>,
94
        /// This value may occur repeatedly.
95
        #[serde(default, skip_serializing_if = "Option::is_none")]
96
        except: Option<SatIntegerView>,
97
        encoding: Option<EncodingSelection<AllDifferentEncoding>>,
98
        amo_encoding: Option<EncodingSelection<AmoEncoding>>,
99
        pb_encoding: Option<EncodingSelection<PbEncoding>>,
100
    },
101
    /// Define membership in an integer/Boolean relation.
102
    Table {
103
        output: Expression,
104
        inputs: Vec<SatIntegerView>,
105
        rows: Vec<Vec<i64>>,
106
        /// Non-constant relation cells retain their numeric representation views.
107
        #[serde(default, skip_serializing_if = "Option::is_none")]
108
        row_views: Option<Vec<Vec<SatIntegerView>>>,
109
        negative: bool,
110
        encoding: Option<EncodingSelection<TableEncoding>>,
111
        pb_encoding: Option<EncodingSelection<PbEncoding>>,
112
    },
113
    /// Define a selected scalar value; out-of-domain indices leave it unconstrained.
114
    Element {
115
        index_view: SatIntegerView,
116
        value: SatIntegerView,
117
        entries: Vec<(i64, SatIntegerView)>,
118
        encoding: Option<EncodingSelection<ElementEncoding>>,
119
        pb_encoding: Option<EncodingSelection<PbEncoding>>,
120
    },
121
    /// Define an output as equivalent to a semantic Boolean expression.
122
    Boolean {
123
        output: Expression,
124
        expression: Expression,
125
    },
126
}
127

            
128
impl Display for SatEncodingDecision {
129
67122
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
130
67122
        match self {
131
            Self::AtMostOne { inputs, encoding } => write!(
132
                f,
133
                "at-most-one({}) using {:?}",
134
                inputs
135
                    .iter()
136
                    .map(ToString::to_string)
137
                    .collect::<Vec<_>>()
138
                    .join(", "),
139
                encoding
140
            ),
141
            Self::Cardinality {
142
                inputs,
143
                relation,
144
                bound,
145
                encoding,
146
754
            } => write!(
147
754
                f,
148
754
                "count({}) {:?} {bound} using {encoding:?}",
149
754
                inputs
150
754
                    .iter()
151
754
                    .map(ToString::to_string)
152
754
                    .collect::<Vec<_>>()
153
754
                    .join(", "),
154
754
                relation
155
            ),
156
            Self::CountRelation {
157
                output,
158
                inputs,
159
                relation,
160
                bound,
161
                encoding,
162
                amo_encoding,
163
9396
            } => write!(
164
9396
                f,
165
9396
                "define({output} <-> count({}) {relation:?} {bound}) using {encoding:?}, AMO {amo_encoding:?}",
166
9396
                inputs
167
9396
                    .iter()
168
9396
                    .map(ToString::to_string)
169
9396
                    .collect::<Vec<_>>()
170
9396
                    .join(", "),
171
            ),
172
            Self::PseudoBoolean {
173
                terms,
174
                groups,
175
                relation,
176
                bound,
177
                encoding,
178
            } => {
179
410
                write!(
180
410
                    f,
181
410
                    "weighted({}) {relation:?} {bound} using {encoding:?}",
182
410
                    terms
183
410
                        .iter()
184
9466
                        .map(|(weight, input)| format!("{weight}*{input}"))
185
                        .collect::<Vec<_>>()
186
410
                        .join(", ")
187
                )?;
188
410
                if !groups.is_empty() {
189
406
                    write!(f, " with {groups:?}")?;
190
4
                }
191
410
                Ok(())
192
            }
193
            Self::IntegerRelation {
194
                output,
195
                terms,
196
                groups,
197
                relation,
198
                bound,
199
                encoding,
200
16362
            } => write!(
201
16362
                f,
202
16362
                "define({output} <-> integer-weighted({}) {relation:?} {bound}) using {encoding:?} with {groups:?}",
203
16362
                terms
204
16362
                    .iter()
205
63300
                    .map(|(weight, input)| format!("{weight}*{input}"))
206
                    .collect::<Vec<_>>()
207
16362
                    .join(", ")
208
            ),
209
            Self::AllDifferent {
210
                output,
211
                inputs,
212
                except,
213
                comparisons,
214
                encoding,
215
                amo_encoding,
216
                pb_encoding,
217
            } => {
218
1566
                let inputs = inputs
219
1566
                    .iter()
220
1566
                    .map(ToString::to_string)
221
1566
                    .collect::<Vec<_>>()
222
1566
                    .join(", ");
223
1566
                let constraint = if let Some(comparisons) = comparisons {
224
                    format!(
225
                        "allDifferentComparisons({})",
226
                        comparisons
227
                            .iter()
228
                            .map(ToString::to_string)
229
                            .collect::<Vec<_>>()
230
                            .join(", ")
231
                    )
232
                } else {
233
1566
                    match except {
234
                        Some(value) if value.terms.is_empty() => {
235
                            format!("allDifferentExcept({inputs}, {})", value.constant)
236
                        }
237
                        Some(value) => format!("allDifferentExcept({inputs}, {value})"),
238
1566
                        None => format!("allDifferent({inputs})"),
239
                    }
240
                };
241
1566
                write!(
242
1566
                    f,
243
1566
                    "define({output} <-> {constraint}) using {encoding:?}, AMO {amo_encoding:?}, PB {pb_encoding:?}"
244
                )
245
            }
246
            Self::Table {
247
                output,
248
                inputs,
249
                rows,
250
                row_views,
251
                negative,
252
                encoding,
253
                pb_encoding,
254
            } => write!(
255
                f,
256
                "define({output} <-> table({}, {}, negative={negative})) using {encoding:?}, PB {pb_encoding:?}",
257
                inputs
258
                    .iter()
259
                    .map(ToString::to_string)
260
                    .collect::<Vec<_>>()
261
                    .join(", "),
262
                row_views
263
                    .as_ref()
264
                    .map_or_else(|| format!("{rows:?}"), |views| format!("{views:?}"))
265
            ),
266
            Self::Element {
267
                index_view,
268
                value,
269
                entries,
270
                encoding,
271
                pb_encoding,
272
58
            } => write!(
273
58
                f,
274
58
                "element(index={index_view}, value={value}, entries=[{}]) using {encoding:?}, PB {pb_encoding:?}",
275
58
                entries
276
58
                    .iter()
277
11600
                    .map(|(label, view)| format!("{label}: {view}"))
278
                    .collect::<Vec<_>>()
279
58
                    .join(", ")
280
            ),
281
            Self::Objective {
282
                minimise,
283
                value,
284
                encoding,
285
48
            } => write!(
286
48
                f,
287
48
                "optimise(minimise={minimise}, value={value}) using {encoding:?}"
288
            ),
289
27824
            Self::Assert(expression) => write!(f, "assert({expression}) using tseitin"),
290
            Self::Boolean { output, expression } => {
291
10704
                write!(f, "define({output} <-> {expression}) using tseitin")
292
            }
293
        }
294
67122
    }
295
}
296

            
297
/// Numeric relation between a weighted integer view and its bound.
298
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
299
pub enum IntegerRelation {
300
    Equal,
301
    NotEqual,
302
    Less,
303
    LessEqual,
304
    Greater,
305
    GreaterEqual,
306
}
307

            
308
/// A contiguous group of weighted terms with an existing representation invariant.
309
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
310
pub struct PbTermGroup {
311
    /// First term index, inclusive.
312
    pub start: usize,
313
    /// Last term index, exclusive.
314
    pub end: usize,
315
    /// The relationship between these terms, independent of library literals.
316
    pub structure: PbTermStructure,
317
}
318

            
319
/// Representation structure available to a pseudo-Boolean encoder.
320
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
321
pub enum PbTermStructure {
322
    /// At most one Boolean input in the group is true.
323
    Choice,
324
    /// Each Boolean input is implied by the next input in the group.
325
    Chain,
326
    /// Binary terms bounded by the representation's inclusive domain.
327
    BoundedBinary { lower: i64, upper: i64 },
328
}
329

            
330
impl SatEncodingDecision {
331
    /// Semantic expressions held by this decision.
332
1839416166
    pub fn expressions(&self) -> Vec<&Expression> {
333
1839416166
        match self {
334
            Self::AtMostOne { inputs, .. } | Self::Cardinality { inputs, .. } => {
335
9271329
                inputs.iter().collect()
336
            }
337
61740
            Self::Objective { value, .. } => value
338
61740
                .terms
339
61740
                .iter()
340
                .map(|(_, expression)| expression)
341
                .collect(),
342
            Self::CountRelation { output, inputs, .. } => {
343
1415316
                std::iter::once(output).chain(inputs).collect()
344
            }
345
685050
            Self::PseudoBoolean { terms, .. } => terms.iter().map(|(_, input)| input).collect(),
346
26620452
            Self::IntegerRelation { output, terms, .. } => std::iter::once(output)
347
26620452
                .chain(terms.iter().map(|(_, input)| input))
348
                .collect(),
349
            Self::AllDifferent {
350
                output,
351
                inputs,
352
                except,
353
                comparisons,
354
                ..
355
445875
            } => std::iter::once(output)
356
445875
                .chain(comparisons.iter().flatten())
357
1308306
                .chain(inputs.iter().chain(except.iter()).flat_map(|input| {
358
1308306
                    input
359
1308306
                        .terms
360
1308306
                        .iter()
361
                        .map(|(_, term)| term)
362
1308306
                        .chain(input.choices.iter().flatten().map(|(_, term)| term))
363
1308306
                }))
364
                .collect(),
365
            Self::Table {
366
                output,
367
                inputs,
368
                row_views,
369
                ..
370
524958
            } => std::iter::once(output)
371
                .chain(
372
524958
                    inputs
373
524958
                        .iter()
374
524958
                        .chain(row_views.iter().flatten().flatten())
375
2068164
                        .flat_map(|input| {
376
2068164
                            input
377
2068164
                                .terms
378
2068164
                                .iter()
379
                                .map(|(_, term)| term)
380
2068164
                                .chain(input.choices.iter().flatten().map(|(_, term)| term))
381
2068164
                        }),
382
                )
383
                .collect(),
384
            Self::Element {
385
                index_view,
386
                value,
387
                entries,
388
                ..
389
837549
            } => std::iter::once(index_view)
390
837549
                .chain(std::iter::once(value))
391
837549
                .chain(entries.iter().map(|(_, entry)| entry))
392
6033015
                .flat_map(|view| {
393
6033015
                    view.terms
394
6033015
                        .iter()
395
                        .map(|(_, term)| term)
396
6033015
                        .chain(view.choices.iter().flatten().map(|(_, term)| term))
397
6033015
                })
398
                .collect(),
399
46007292
            Self::Assert(expression) => vec![expression],
400
1753546605
            Self::Boolean { output, expression } => vec![output, expression],
401
        }
402
1839416166
    }
403
}
404

            
405
/// A numeric view whose representation constraints remain independently enforced.
406
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
407
#[biplate(to=Expression)]
408
#[biplate(to=crate::ast::DeclarationPtr)]
409
pub struct SatIntegerView {
410
    /// Constant contribution to the actual numeric value.
411
    pub constant: i64,
412
    /// Signed weights over Boolean representation operands.
413
    pub terms: Vec<(i64, Expression)>,
414
    /// Independently guaranteed representation structure.
415
    pub groups: Vec<PbTermGroup>,
416
    /// Actual values and their exactly-one indicators, when available.
417
    pub choices: Option<Vec<(i64, Expression)>>,
418
}
419

            
420
impl Display for SatIntegerView {
421
17332
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
422
17332
        write!(
423
17332
            f,
424
            "{}+weighted({}) with {:?}",
425
17332
            self.constant,
426
17332
            self.terms
427
17332
                .iter()
428
25440
                .map(|(weight, input)| format!("{weight}*{input}"))
429
                .collect::<Vec<_>>()
430
17332
                .join(", "),
431
17332
            self.groups
432
        )?;
433
17332
        if let Some(choices) = &self.choices {
434
11658
            write!(
435
11658
                f,
436
                " choices({})",
437
11658
                choices
438
11658
                    .iter()
439
23316
                    .map(|(value, input)| format!("{value}:{input}"))
440
                    .collect::<Vec<_>>()
441
11658
                    .join(", ")
442
            )?;
443
5674
        }
444
17332
        Ok(())
445
17332
    }
446
}
447

            
448
/// Library compositions available for element.
449
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
450
pub enum ElementEncoding {
451
    /// Each valid index selector implies its scalar equality.
452
    Implication,
453
    /// One support disjunction, with an explicit out-of-domain alternative.
454
    Support,
455
}
456
encoding_labels!(ElementEncoding, "element encoding", 2, [
457
    Implication => "implication",
458
    Support => "support",
459
]);
460

            
461
/// Library compositions available for table.
462
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
463
pub enum TableEncoding {
464
    /// Disjunction of matching rows, sharing each numeric cell comparison.
465
    Tuple,
466
    /// Reduced layered decision diagram, sharing equal suffix relations.
467
    Mdd,
468
    /// Bidirectional value supports for constant binary relations.
469
    BinarySupport,
470
}
471
encoding_labels!(TableEncoding, "table encoding", 3, [
472
    Tuple => "tuple",
473
    Mdd => "mdd",
474
    BinarySupport => "binary-support",
475
]);
476

            
477
/// Library compositions available for allDifferent.
478
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
479
pub enum AllDifferentEncoding {
480
    /// Pairwise numeric disequalities, for every numeric representation.
481
    Pairwise,
482
    /// AMO over equal-value indicators; requires choice views for all operands.
483
    ValueAmo,
484
}
485
encoding_labels!(AllDifferentEncoding, "allDifferent encoding", 2, [
486
    Pairwise => "pairwise",
487
    ValueAmo => "value-amo",
488
]);
489

            
490
/// The origin of a resolved compilation choice.
491
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
492
pub enum SelectionProvenance {
493
    ExplicitConfiguration,
494
    Heuristic,
495
}
496

            
497
/// A resolved algorithm and the source of that choice, reusable across encoding classes.
498
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
499
pub struct EncodingSelection<T> {
500
    pub algorithm: T,
501
    pub provenance: SelectionProvenance,
502
}
503

            
504
/// Library algorithms available for asserted and reified at-most-one constraints.
505
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
506
pub enum AmoEncoding {
507
    Pairwise,
508
    Ladder,
509
    Bitwise,
510
    Commander,
511
    Bimander,
512
    TwoProduct,
513
    PindakaasPairwise,
514
    PindakaasLadder,
515
    PindakaasBitwise,
516
}
517
encoding_labels!(AmoEncoding, "AMO encoding", 9, [
518
    Pairwise => "pairwise",
519
    Ladder => "ladder",
520
    Bitwise => "bitwise",
521
    Commander => "commander",
522
    Bimander => "bimander",
523
    TwoProduct => "two-product",
524
    PindakaasPairwise => "pindakaas-pairwise",
525
    PindakaasLadder => "pindakaas-ladder",
526
    PindakaasBitwise => "pindakaas-bitwise",
527
]);
528

            
529
/// Bound direction for a semantic cardinality constraint.
530
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
531
pub enum CardinalityRelation {
532
    AtMost,
533
    AtLeast,
534
    Exactly,
535
}
536
/// Provider and algorithm for a cardinality constraint.
537
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
538
pub enum CardinalityEncoding {
539
    RustsatTotalizer,
540
    PindakaasSortingNetwork,
541
}
542
encoding_labels!(CardinalityEncoding, "cardinality encoder", 2, [
543
    RustsatTotalizer => "rustsat-totalizer",
544
    PindakaasSortingNetwork => "pindakaas-sorting-network",
545
]);
546
/// Provider and algorithm for signed weighted pseudo-Boolean constraints.
547
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
548
pub enum PbEncoding {
549
    RustsatGeneralizedTotalizer,
550
    RustsatBinaryAdder,
551
    PindakaasBdd,
552
    RustsatDynamicPolyWatchdog,
553
    PindakaasSwc,
554
}
555
encoding_labels!(PbEncoding, "pseudo-Boolean encoder", 5, [
556
    RustsatGeneralizedTotalizer => "rustsat-generalized-totalizer",
557
    RustsatBinaryAdder => "rustsat-binary-adder",
558
    PindakaasBdd => "pindakaas-bdd",
559
    RustsatDynamicPolyWatchdog => "rustsat-dynamic-poly-watchdog",
560
    PindakaasSwc => "pindakaas-swc",
561
]);