1
//! Resolve semantic SAT encoding decisions through pins and modelling heuristics.
2
use crate::ast::Expression;
3
use crate::ast::sat_decision::*;
4

            
5
/// Pins take precedence; a singleton family consumes no portfolio choice.
6
2523664
fn select_encoding<T: Copy>(
7
2523664
    pinned: Option<T>,
8
2523664
    algorithms: &[T],
9
2523664
    labels: &[&str],
10
2523664
    compact: impl FnOnce() -> usize,
11
2523664
) -> (T, SelectionProvenance) {
12
    use crate::settings::{self, Heuristic};
13
2523664
    if let Some(algorithm) = pinned {
14
56
        return (algorithm, SelectionProvenance::ExplicitConfiguration);
15
2523608
    }
16
2523608
    let index = if algorithms.len() == 1 {
17
24244
        0
18
    } else {
19
2499364
        match settings::heuristic() {
20
179684
            Heuristic::First => 0,
21
5248
            Heuristic::Compact => compact(),
22
            Heuristic::Random => settings::next_heuristic_random_index(algorithms.len()),
23
            Heuristic::Interactive => settings::next_heuristic_interactive_index(labels),
24
2314432
            Heuristic::All => settings::next_heuristic_all_index(labels),
25
        }
26
    };
27
2523608
    (algorithms[index], SelectionProvenance::Heuristic)
28
2523664
}
29

            
30
/// Resolve unpinned encoding classes through the configured heuristic.
31
/// Compact chooses AMO and table algorithms per constraint; portfolio heuristics share families.
32
1351539
pub(crate) fn resolve_encoding_choices(decisions: &mut [SatEncodingDecision]) {
33
1351539
    resolve_element_choices(decisions);
34
1351539
    resolve_table_choices(decisions);
35
1351539
    resolve_alldifferent_choices(decisions);
36
1351539
    resolve_pb_choices(decisions);
37
1351539
    resolve_cardinality_choices(decisions);
38
1351539
    resolve_amo_choices(decisions);
39
1351539
}
40
1351539
fn resolve_element_choices(decisions: &mut [SatEncodingDecision]) {
41
    use crate::settings;
42
1351539
    let sizes = decisions
43
1351539
        .iter()
44
613138722
        .filter_map(|decision| match decision {
45
            SatEncodingDecision::Element {
46
                entries,
47
                encoding: None,
48
                ..
49
279183
            } => Some(entries.len()),
50
612859539
            _ => None,
51
613138722
        })
52
        .collect::<Vec<_>>();
53
1351539
    if sizes.is_empty() {
54
1208337
        return;
55
143202
    }
56
143202
    let algorithms = &ElementEncoding::ALL;
57
143202
    let (algorithm, provenance) = select_encoding(
58
143202
        settings::element_encoding(),
59
143202
        algorithms,
60
143202
        &ElementEncoding::LABELS,
61
7917
        || usize::from(sizes.iter().any(|size| *size > 4)),
62
    );
63
143202
    for decision in decisions {
64
43271074
        if let SatEncodingDecision::Element { encoding, .. } = decision
65
279183
            && encoding.is_none()
66
        {
67
279183
            *encoding = Some(EncodingSelection {
68
279183
                algorithm,
69
279183
                provenance,
70
279183
            });
71
42991891
        }
72
    }
73
1351539
}
74
1351539
fn resolve_table_choices(decisions: &mut [SatEncodingDecision]) {
75
    use crate::settings::{self, Heuristic};
76
1351539
    if settings::table_encoding().is_none() && settings::heuristic() == Heuristic::Compact {
77
13624
        for decision in decisions {
78
4081447
            if let SatEncodingDecision::Table {
79
4081447
                rows,
80
4081447
                row_views,
81
4081447
                encoding,
82
4081447
                ..
83
4081447
            } = decision
84
2436
                && encoding.is_none()
85
            {
86
2436
                let algorithm = compact_table_encoding(rows, row_views.as_ref().map(Vec::len));
87
2436
                *encoding = Some(EncodingSelection {
88
2436
                    algorithm,
89
2436
                    provenance: SelectionProvenance::Heuristic,
90
2436
                });
91
4079011
            }
92
        }
93
13624
        return;
94
1337915
    }
95
1337915
    let rows = decisions
96
1337915
        .iter()
97
609057275
        .filter_map(|decision| match decision {
98
            SatEncodingDecision::Table {
99
                rows,
100
                encoding: None,
101
                ..
102
172550
            } => Some(rows.len()),
103
608884725
            _ => None,
104
609057275
        })
105
        .collect::<Vec<_>>();
106
1337915
    if rows.is_empty() {
107
1256715
        return;
108
81200
    }
109
7392390
    let binary = decisions.iter().all(|decision| match decision {
110
        SatEncodingDecision::Table {
111
            inputs, row_views, ..
112
111650
        } => inputs.len() == 2 && row_views.is_none(),
113
7280740
        _ => true,
114
7392390
    });
115
81200
    let algorithms = &TableEncoding::ALL[..if binary { 3 } else { 2 }];
116
81200
    let labels = &TableEncoding::LABELS[..algorithms.len()];
117
    let (algorithm, provenance) =
118
81200
        select_encoding(settings::table_encoding(), algorithms, labels, || {
119
            usize::from(rows.iter().any(|size| *size > 4))
120
        });
121
81200
    for decision in decisions {
122
7950640
        if let SatEncodingDecision::Table { encoding, .. } = decision
123
172550
            && encoding.is_none()
124
        {
125
172550
            *encoding = Some(EncodingSelection {
126
172550
                algorithm,
127
172550
                provenance,
128
172550
            });
129
7778090
        }
130
    }
131
1351539
}
132

            
133
/// Count real variable rows or distinct constant rows before choosing the composition.
134
2436
fn compact_table_encoding(rows: &[Vec<i64>], variable_row_count: Option<usize>) -> TableEncoding {
135
2436
    let count = variable_row_count
136
2436
        .unwrap_or_else(|| rows.iter().collect::<std::collections::BTreeSet<_>>().len());
137
2436
    if count > 4 {
138
2436
        TableEncoding::Mdd
139
    } else {
140
        TableEncoding::Tuple
141
    }
142
2436
}
143
1351539
fn resolve_alldifferent_choices(decisions: &mut [SatEncodingDecision]) {
144
    use crate::settings;
145
1351539
    let eligible: Vec<_> = decisions
146
1351539
        .iter()
147
613138722
        .filter_map(|decision| match decision {
148
            SatEncodingDecision::AllDifferent {
149
                inputs,
150
                comparisons,
151
                encoding: None,
152
                ..
153
291595
            } => Some(comparisons.is_none() && inputs.iter().all(|input| input.choices.is_some())),
154
612990097
            _ => None,
155
613138722
        })
156
        .collect();
157
1351539
    if eligible.is_empty() {
158
1288145
        return;
159
63394
    }
160
92104
    let algorithms = if eligible.iter().all(|eligible| *eligible) {
161
39150
        &AllDifferentEncoding::ALL[..]
162
    } else {
163
24244
        &AllDifferentEncoding::ALL[..1]
164
    };
165
63394
    let (algorithm, provenance) = select_encoding(
166
63394
        settings::alldifferent_encoding(),
167
63394
        algorithms,
168
63394
        &AllDifferentEncoding::LABELS[..algorithms.len()],
169
        || 1,
170
    );
171
63394
    for decision in decisions {
172
4899753
        if let SatEncodingDecision::AllDifferent { encoding, .. } = decision
173
148625
            && encoding.is_none()
174
        {
175
148625
            *encoding = Some(EncodingSelection {
176
148625
                algorithm,
177
148625
                provenance,
178
148625
            });
179
4751128
        }
180
    }
181
1351539
}
182
/// Whether a count comparison needs an upper threshold of one after constant folding.
183
918314
pub(crate) fn count_uses_amo(inputs: &[Expression], relation: IntegerRelation, bound: i64) -> bool {
184
    let (constants, variables) =
185
918314
        inputs
186
918314
            .iter()
187
            .fold(
188
918314
                (0i128, 0usize),
189
2809984
                |(constants, variables), input| match crate::ast::eval_constant(input) {
190
45704
                    Some(crate::ast::Literal::Bool(true)) => (constants + 1, variables),
191
32364
                    Some(crate::ast::Literal::Bool(false)) => (constants, variables),
192
2731916
                    _ => (constants, variables + 1),
193
2809984
                },
194
            );
195
918314
    if variables <= 1 {
196
13340
        return false;
197
904974
    }
198
904974
    let bound = i128::from(bound) - constants;
199
904974
    match relation {
200
302760
        IntegerRelation::Less | IntegerRelation::GreaterEqual => bound - 1 == 1,
201
442018
        IntegerRelation::LessEqual | IntegerRelation::Greater => bound == 1,
202
160196
        IntegerRelation::Equal | IntegerRelation::NotEqual => bound == 1 || bound - 1 == 1,
203
    }
204
918314
}
205

            
206
1351539
fn resolve_amo_choices(decisions: &mut [SatEncodingDecision]) {
207
    use crate::settings::{self, Heuristic};
208
1351539
    let compact = settings::amo_encoding().is_none() && settings::heuristic() == Heuristic::Compact;
209
1351539
    let largest = decisions
210
1351539
        .iter()
211
613138722
        .filter_map(|decision| match decision {
212
            SatEncodingDecision::AtMostOne {
213
                inputs,
214
                encoding: None,
215
2823034
            } => Some(inputs.len()),
216
            SatEncodingDecision::CountRelation {
217
                inputs,
218
                relation,
219
                bound,
220
                amo_encoding: None,
221
                ..
222
471772
            } if count_uses_amo(inputs, *relation, *bound) => Some(inputs.len()),
223
            SatEncodingDecision::AllDifferent {
224
                inputs,
225
                encoding: Some(selection),
226
                amo_encoding: None,
227
                ..
228
148625
            } if selection.algorithm == AllDifferentEncoding::ValueAmo => Some(inputs.len()),
229
610014262
            _ => None,
230
613138722
        })
231
        .max();
232
1351539
    let Some(largest) = largest else {
233
537944
        return;
234
    };
235
813595
    let (algorithm, provenance) = select_encoding(
236
813595
        settings::amo_encoding(),
237
813595
        &AmoEncoding::ALL,
238
813595
        &AmoEncoding::LABELS,
239
58
        || usize::from(largest > 5),
240
    );
241
813595
    for decision in decisions {
242
284440178
        let algorithm = if compact {
243
33698
            let size = match &*decision {
244
                SatEncodingDecision::AtMostOne { inputs, .. }
245
290
                | SatEncodingDecision::CountRelation { inputs, .. } => inputs.len(),
246
                SatEncodingDecision::AllDifferent { inputs, .. } => inputs.len(),
247
33408
                _ => 0,
248
            };
249
33698
            compact_amo_encoding(size)
250
        } else {
251
284406480
            algorithm
252
        };
253
284440178
        if let SatEncodingDecision::CountRelation {
254
284440178
            inputs,
255
284440178
            relation,
256
284440178
            bound,
257
284440178
            amo_encoding,
258
284440178
            ..
259
284440178
        } = decision
260
446542
            && amo_encoding.is_none()
261
446542
            && count_uses_amo(inputs, *relation, *bound)
262
        {
263
267496
            *amo_encoding = Some(EncodingSelection {
264
267496
                algorithm,
265
267496
                provenance,
266
267496
            });
267
284172682
        }
268
284440178
        if let SatEncodingDecision::AtMostOne { encoding, .. }
269
284440178
        | SatEncodingDecision::AllDifferent {
270
284440178
            amo_encoding: encoding,
271
284440178
            encoding:
272
284440178
                Some(EncodingSelection {
273
284440178
                    algorithm: AllDifferentEncoding::ValueAmo,
274
284440178
                    ..
275
284440178
                }),
276
284440178
            ..
277
284440178
        } = decision
278
2856964
            && encoding.is_none()
279
        {
280
2856964
            *encoding = Some(EncodingSelection {
281
2856964
                algorithm,
282
2856964
                provenance,
283
2856964
            });
284
281583214
        }
285
    }
286
1351539
}
287

            
288
33698
fn compact_amo_encoding(size: usize) -> AmoEncoding {
289
33698
    if size <= 5 {
290
33466
        AmoEncoding::Pairwise
291
    } else {
292
232
        AmoEncoding::Ladder
293
    }
294
33698
}
295

            
296
1351539
fn resolve_cardinality_choices(decisions: &mut [SatEncodingDecision]) {
297
    use crate::settings;
298
599596680
    if !decisions.iter().any(|decision| {
299
599596679
        matches!(
300
599596679
            decision,
301
            SatEncodingDecision::Cardinality { encoding: None, .. }
302
                | SatEncodingDecision::CountRelation { encoding: None, .. }
303
        )
304
599596679
    }) {
305
1135373
        return;
306
216166
    }
307
216166
    let (algorithm, provenance) = select_encoding(
308
216166
        settings::cardinality_encoding(),
309
216166
        &CardinalityEncoding::ALL,
310
216166
        &CardinalityEncoding::LABELS,
311
        || 0,
312
    );
313
216166
    for decision in decisions {
314
16619378
        if let SatEncodingDecision::Cardinality { encoding, .. }
315
16619378
        | SatEncodingDecision::CountRelation { encoding, .. } = decision
316
739181
            && encoding.is_none()
317
        {
318
739181
            *encoding = Some(EncodingSelection {
319
739181
                algorithm,
320
739181
                provenance,
321
739181
            });
322
15880197
        }
323
    }
324
1351539
}
325

            
326
1351539
fn resolve_pb_choices(decisions: &mut [SatEncodingDecision]) {
327
    use crate::settings;
328
1351539
    let unresolved: Vec<_> = decisions
329
1351539
        .iter()
330
613138722
        .filter_map(|decision| match decision {
331
            SatEncodingDecision::PseudoBoolean {
332
                terms,
333
                encoding: None,
334
                ..
335
            }
336
            | SatEncodingDecision::IntegerRelation {
337
                terms,
338
                encoding: None,
339
                ..
340
9101834
            } => Some(terms),
341
            SatEncodingDecision::Objective {
342
                value,
343
                encoding: None,
344
                ..
345
20580
            } => Some(&value.terms),
346
            SatEncodingDecision::AllDifferent {
347
                inputs,
348
                except,
349
                pb_encoding: None,
350
                ..
351
148625
            } => inputs
352
148625
                .iter()
353
148625
                .chain(except.iter())
354
436102
                .max_by_key(|input| {
355
436102
                    input
356
436102
                        .terms
357
436102
                        .iter()
358
4731089
                        .map(|(weight, _)| i128::from(*weight).abs())
359
                        .sum::<i128>()
360
436102
                })
361
95845
                .map(|input| &input.terms),
362
            SatEncodingDecision::Table {
363
                inputs,
364
                row_views,
365
                pb_encoding: None,
366
                ..
367
174986
            } => inputs
368
174986
                .iter()
369
174986
                .chain(row_views.iter().flatten().flatten())
370
689388
                .max_by_key(|input| {
371
689388
                    input
372
689388
                        .terms
373
689388
                        .iter()
374
2195706
                        .map(|(weight, _)| i128::from(*weight).abs())
375
                        .sum::<i128>()
376
689388
                })
377
174986
                .map(|input| &input.terms),
378
            SatEncodingDecision::Element {
379
                index_view,
380
                value,
381
                entries,
382
                pb_encoding: None,
383
                ..
384
279183
            } => std::iter::once(index_view)
385
279183
                .chain(std::iter::once(value))
386
279183
                .chain(entries.iter().map(|(_, entry)| entry))
387
2011005
                .max_by_key(|view| {
388
2011005
                    view.terms
389
2011005
                        .iter()
390
4834909
                        .map(|(weight, _)| i128::from(*weight).abs())
391
                        .sum::<i128>()
392
2011005
                })
393
279183
                .map(|view| &view.terms),
394
603413514
            _ => None,
395
613138722
        })
396
        .collect();
397
1351539
    if unresolved.is_empty() {
398
145432
        return;
399
1206107
    }
400
1206107
    let (algorithm, provenance) = select_encoding(
401
1206107
        settings::pb_encoding(),
402
1206107
        &PbEncoding::ALL,
403
1206107
        &PbEncoding::LABELS,
404
4088
        || {
405
457161
            usize::from(unresolved.iter().any(|terms| {
406
457161
                terms
407
457161
                    .iter()
408
1614716
                    .map(|(weight, _)| i128::from(*weight).abs())
409
                    .sum::<i128>()
410
457161
                    > 4096
411
457161
            }))
412
4088
        },
413
    );
414
1206107
    for decision in decisions {
415
612776077
        if let SatEncodingDecision::PseudoBoolean { encoding, .. }
416
612776077
        | SatEncodingDecision::IntegerRelation { encoding, .. }
417
612776077
        | SatEncodingDecision::Objective { encoding, .. }
418
612776077
        | SatEncodingDecision::AllDifferent {
419
612776077
            pb_encoding: encoding,
420
612776077
            ..
421
612776077
        }
422
612776077
        | SatEncodingDecision::Table {
423
612776077
            pb_encoding: encoding,
424
612776077
            ..
425
612776077
        }
426
612776077
        | SatEncodingDecision::Element {
427
612776077
            pb_encoding: encoding,
428
612776077
            ..
429
612776077
        } = decision
430
9725208
            && encoding.is_none()
431
        {
432
9725208
            *encoding = Some(EncodingSelection {
433
9725208
                algorithm,
434
9725208
                provenance,
435
9725208
            });
436
603050869
        }
437
    }
438
1351539
}