Skip to content

Commit 296d9a3

Browse files
feat(t-wise): add option to only fully cover specific literals
1 parent 841c130 commit 296d9a3

13 files changed

Lines changed: 166 additions & 44 deletions

File tree

bindings/kotlin/src/main/kotlin/de/softvare/ddnnife/java-utils.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,33 @@ fun atomicSets(ddnnf: DdnnfMut, candidates: List<Int>?, assumptions: List<Int>,
4343
return ddnnf.atomicSets(candidatesUInt, assumptions, cross)
4444
}
4545

46-
fun sampleTWise(ddnnf: Ddnnf, t: Int): SamplingResult {
46+
fun sampleTWise(ddnnf: Ddnnf, t: Int, literals: List<Int>?): SamplingResult {
4747
require(t >= 0) { "t must be positive." }
48-
return ddnnf.sampleTWise(t.toULong())
48+
return ddnnf.sampleTWise(t.toULong(), literals)
4949
}
5050

51-
fun isSample(result: SamplingResult): Boolean = when(result) {
51+
fun isSample(result: SamplingResult): Boolean = when (result) {
5252
is SamplingResult.ResultWithSample -> true
5353
else -> false
5454
}
5555

56-
fun isEmpty(result: SamplingResult): Boolean = when(result) {
56+
fun isEmpty(result: SamplingResult): Boolean = when (result) {
5757
is SamplingResult.Empty -> true
5858
else -> false
5959
}
6060

61-
fun isVoid(result: SamplingResult): Boolean = when(result) {
61+
fun isVoid(result: SamplingResult): Boolean = when (result) {
6262
is SamplingResult.Void -> true
6363
else -> false
6464
}
6565

6666
@Throws(IllegalArgumentException::class)
6767
fun getSample(result: SamplingResult): Sample {
68-
when(result) {
68+
when (result) {
6969
is SamplingResult.ResultWithSample -> {
7070
return result.v1
7171
}
72+
7273
else -> {
7374
throw IllegalArgumentException("Invalid sampling result type: ${result::class}")
7475
}

bindings/kotlin/src/test/java/de/softvare/ddnnife/JavaTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void atomicSetsTest() {
6969

7070
@Test
7171
void tWise() {
72-
SamplingResult result = sampleTWise(ddnnf, 1);
72+
SamplingResult result = sampleTWise(ddnnf, 1, null);
7373
assertTrue(isSample(result));
7474

7575
Sample sample = getSample(result);

bindings/kotlin/src/test/kotlin/de/softvare/ddnnife/DdnnfTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ internal class DdnnfTest {
6666

6767
@Test
6868
fun tWise() {
69-
val result = ddnnf.sampleTWise(1u)
69+
val result = ddnnf.sampleTWise(1u, null)
7070
when(result) {
7171
is SamplingResult.ResultWithSample -> {
7272
val sample = result.v1

bindings/python/tests/test_ddnnf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
ddnnf = Ddnnf.from_file("../../example_input/busybox-1.18.0_c2d.nnf", None)
44
features = 854
55

6+
67
def test_sat():
78
ddnnf_mut = ddnnf.as_mut()
89
assert ddnnf_mut.is_sat([])
@@ -31,6 +32,7 @@ def test_core_and_dead():
3132
core = ddnnf_mut.dead([])
3233
assert len(core) == 18
3334

35+
3436
def test_core():
3537
core = ddnnf.get_core()
3638
assert len(core) == 41
@@ -54,7 +56,7 @@ def test_atomic_sets():
5456

5557

5658
def test_t_wise():
57-
sample = ddnnf.sample_t_wise(1)
59+
sample = ddnnf.sample_t_wise(1, None)
5860
assert sample.is_RESULT_WITH_SAMPLE()
5961
assert len(sample[0].vars) == features
6062

ddnnife/benches/t_wise.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ fn bench_t_wise(c: &mut Criterion, name: &str, path: &Path, t: usize) {
77
let ddnnf = Ddnnf::from_file(path, None);
88
let id = format!("t-wise {name} t={t}");
99
c.bench_function(&id, |bencher| {
10-
bencher.iter(|| ddnnf.sample_t_wise(black_box(t)))
10+
bencher.iter(|| ddnnf.sample_t_wise(black_box(t), None))
1111
});
1212
}
1313

ddnnife/src/ddnnf/anomalies/t_wise_sampling.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod t_wise_sampler;
99

1010
use crate::ddnnf::extended_ddnnf::ExtendedDdnnf;
1111
use crate::ddnnf::extended_ddnnf::objective_function::FloatOrd;
12+
use crate::int_hash::IntSet;
1213
use crate::{Ddnnf, DdnnfKind};
1314
use SamplingResult::ResultWithSample;
1415
pub use config::Config;
@@ -29,17 +30,18 @@ use t_wise_sampler::{complete_partial_configs_optimal, trim_and_resample};
2930

3031
impl Ddnnf {
3132
/// Generates samples so that all t-wise interactions between literals are covered.
32-
pub fn sample_t_wise(&self, t: usize) -> SamplingResult {
33+
pub fn sample_t_wise(&self, t: usize, literals: Option<&IntSet<i32>>) -> SamplingResult {
3334
// Setup everything needed for the sampling process.
3435
let sat_solver = SatWrapper::new(self);
3536
let and_merger = ZippingMerger {
3637
t,
3738
sat_solver: &sat_solver,
3839
ddnnf: self,
40+
literals,
3941
};
40-
let or_merger = SimilarityMerger { t };
42+
let or_merger = SimilarityMerger { t, literals };
4143

42-
TWiseSampler::new(self, and_merger, or_merger).sample(t)
44+
TWiseSampler::new(self, and_merger, or_merger, literals).sample(t)
4345
}
4446
}
4547

@@ -59,7 +61,7 @@ impl ExtendedDdnnf {
5961
};
6062
let or_merger = AttributeSimilarityMerger { t, ext_ddnnf: self };
6163

62-
let mut sampler = TWiseSampler::new(&self.ddnnf, and_merger, or_merger);
64+
let mut sampler = TWiseSampler::new(&self.ddnnf, and_merger, or_merger, None);
6365

6466
for node_id in 0..sampler.ddnnf.nodes.len() {
6567
let partial_sample = sampler.partial_sample(node_id);
@@ -88,6 +90,7 @@ impl ExtendedDdnnf {
8890
t,
8991
self.ddnnf.number_of_variables as usize,
9092
&sat_solver,
93+
None,
9194
);
9295

9396
complete_partial_configs_optimal(&mut sample, self);
@@ -135,6 +138,7 @@ impl ExtendedDdnnf {
135138
t,
136139
self.ddnnf.number_of_variables as usize,
137140
&sat_solver,
141+
None,
138142
);
139143
complete_partial_configs_optimal(&mut sample, self);
140144

@@ -198,7 +202,7 @@ mod test {
198202
let vp9: Ddnnf = build_ddnnf(Path::new("tests/data/VP9_d4.nnf"), Some(42));
199203

200204
for t in 1..=4 {
201-
check_validity_of_sample(vp9.sample_t_wise(t).get_sample().unwrap(), &vp9, t);
205+
check_validity_of_sample(vp9.sample_t_wise(t, None).get_sample().unwrap(), &vp9, t);
202206
}
203207
}
204208

@@ -207,7 +211,11 @@ mod test {
207211
let mut auto1: Ddnnf = build_ddnnf(Path::new("tests/data/auto1_d4.nnf"), Some(2513));
208212
let t = 1;
209213

210-
check_validity_of_sample(auto1.sample_t_wise(t).get_sample().unwrap(), &mut auto1, t);
214+
check_validity_of_sample(
215+
auto1.sample_t_wise(t, None).get_sample().unwrap(),
216+
&mut auto1,
217+
t,
218+
);
211219
}
212220

213221
#[test]

ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample_merger/similarity_merger.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ use std::cmp::{Ordering, min};
99
use streaming_iterator::StreamingIterator;
1010

1111
#[derive(Debug, Copy, Clone)]
12-
pub struct SimilarityMerger {
12+
pub struct SimilarityMerger<'l> {
1313
pub t: usize,
14+
pub literals: Option<&'l IntSet<i32>>,
1415
}
1516

1617
// Mark SimilarityMerger as an OrMerger
17-
impl OrMerger for SimilarityMerger {}
18+
impl OrMerger for SimilarityMerger<'_> {}
1819

19-
impl SampleMerger for SimilarityMerger {
20+
impl SampleMerger for SimilarityMerger<'_> {
2021
fn merge<'a>(&self, _node_id: usize, left: &Sample, right: &Sample) -> Sample {
2122
if left.is_empty() {
2223
return right.clone();
@@ -48,7 +49,7 @@ impl SampleMerger for SimilarityMerger {
4849
.map(|(index, _)| index)
4950
{
5051
let next = candidates.swap_remove(next);
51-
if next.is_t_wise_covered_by(&new_sample, self.t) {
52+
if next.is_t_wise_covered_by(&new_sample, self.t, self.literals) {
5253
continue;
5354
}
5455

@@ -122,7 +123,12 @@ impl<'a> Candidate<'a> {
122123
}
123124
}
124125

125-
fn is_t_wise_covered_by(&self, sample: &Sample, t: usize) -> bool {
126+
fn is_t_wise_covered_by(
127+
&self,
128+
sample: &Sample,
129+
t: usize,
130+
literals: Option<&IntSet<i32>>,
131+
) -> bool {
126132
if self.max_intersect == self.literals.len() {
127133
return true;
128134
}
@@ -139,7 +145,18 @@ impl<'a> Candidate<'a> {
139145
return false;
140146
}
141147

142-
let mut literals: Vec<i32> = self.config.get_decided_literals().collect();
148+
let mut literals: Vec<i32> = self
149+
.config
150+
.get_decided_literals()
151+
.filter(|literal| {
152+
if let Some(literals) = literals {
153+
return literals.contains(literal);
154+
}
155+
156+
true
157+
})
158+
.collect();
159+
143160
literals.shuffle(&mut rng());
144161
debug_assert!(!literals.contains(&0));
145162

@@ -154,7 +171,10 @@ mod test {
154171

155172
#[test]
156173
fn test_similarity_merger() {
157-
let merger = SimilarityMerger { t: 2 };
174+
let merger = SimilarityMerger {
175+
t: 2,
176+
literals: None,
177+
};
158178

159179
let left = Sample::new_from_configs(vec![Config::from(&[1], 1)]);
160180
let right = Sample::new_from_configs(vec![Config::from(&[1], 1)]);
@@ -182,7 +202,7 @@ mod test {
182202
.iter()
183203
.for_each(|c| candidate.update(&c.get_decided_literals().collect::<IntSet<_>>()));
184204

185-
assert!(candidate.is_t_wise_covered_by(&sample, 2));
205+
assert!(candidate.is_t_wise_covered_by(&sample, 2, None));
186206

187207
let mut candidate = Candidate::new(&candidate_config);
188208
let sample = Sample::new_from_configs(vec![
@@ -195,6 +215,6 @@ mod test {
195215
.iter()
196216
.for_each(|c| candidate.update(&c.get_decided_literals().collect::<IntSet<_>>()));
197217

198-
assert!(!candidate.is_t_wise_covered_by(&sample, 2));
218+
assert!(!candidate.is_t_wise_covered_by(&sample, 2, None));
199219
}
200220
}

ddnnife/src/ddnnf/anomalies/t_wise_sampling/sample_merger/zipping_merger.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@ use super::super::{
55
use super::{AndMerger, SampleMerger};
66
use super::{Config, Sample};
77
use crate::Ddnnf;
8+
use crate::int_hash::IntSet;
89
use std::cmp::min;
910
use std::collections::HashSet;
1011
use streaming_iterator::StreamingIterator;
1112

1213
#[derive(Debug, Clone)]
13-
pub struct ZippingMerger<'a> {
14+
pub struct ZippingMerger<'a, 'l> {
1415
pub t: usize,
1516
pub sat_solver: &'a SatWrapper<'a>,
1617
pub ddnnf: &'a Ddnnf,
18+
pub literals: Option<&'l IntSet<i32>>,
1719
}
1820

1921
// Mark ZippingMerger as an AndMerger
20-
impl AndMerger for ZippingMerger<'_> {}
22+
impl AndMerger for ZippingMerger<'_, '_> {}
2123

22-
impl SampleMerger for ZippingMerger<'_> {
24+
impl SampleMerger for ZippingMerger<'_, '_> {
2325
fn merge(&self, node_id: usize, left: &Sample, right: &Sample) -> Sample {
2426
if left.is_empty() {
2527
return right.clone();
@@ -31,7 +33,7 @@ impl SampleMerger for ZippingMerger<'_> {
3133

3234
let mut sample = Self::zip_samples(left, right, self.ddnnf.number_of_variables as usize);
3335

34-
for interaction in Self::interactions(left, right, self.t).iter() {
36+
for interaction in self.interactions(left, right, self.t).iter() {
3537
cover_with_caching_twise(
3638
&mut sample,
3739
interaction,
@@ -68,12 +70,12 @@ impl SampleMerger for ZippingMerger<'_> {
6870
}
6971
}
7072

71-
impl ZippingMerger<'_> {
73+
impl ZippingMerger<'_, '_> {
7274
/// Generates all t-wise interactions between two samples.
73-
fn interactions(left: &Sample, right: &Sample, t: usize) -> Vec<Vec<i32>> {
75+
fn interactions(&self, left: &Sample, right: &Sample, t: usize) -> Vec<Vec<i32>> {
7476
Self::generate_interactions(
75-
Self::generate_self_interactions(left, t),
76-
Self::generate_self_interactions(right, t),
77+
self.generate_self_interactions(left, t),
78+
self.generate_self_interactions(right, t),
7779
)
7880
}
7981

@@ -125,13 +127,25 @@ impl ZippingMerger<'_> {
125127
///
126128
/// In case no interaction of any given size is found, an empty set is still present.
127129
fn generate_self_interactions(
130+
&self,
128131
sample: &Sample,
129132
t: usize,
130133
) -> impl DoubleEndedIterator<Item = HashSet<Vec<i32>>> {
131134
// Extract all decided literals from the existing configs.
132135
let configs: Vec<Vec<i32>> = sample
133136
.iter()
134-
.map(|config| config.get_decided_literals().collect())
137+
.map(|config| {
138+
config
139+
.get_decided_literals()
140+
.filter(|literal| {
141+
if let Some(literals) = self.literals {
142+
return literals.contains(literal);
143+
}
144+
145+
true
146+
})
147+
.collect()
148+
})
135149
.collect();
136150

137151
// From 1 to t ...
@@ -236,6 +250,7 @@ mod test {
236250
t: 3,
237251
sat_solver: &sat_solver,
238252
ddnnf: &ddnnf,
253+
literals: None,
239254
};
240255

241256
let mut left_sample = new_with_literals([2, 3].into_iter().collect(), vec![-2, 3]);

0 commit comments

Comments
 (0)