令 f 和 g 为布尔表达式。
递归分解
d(f and g) = d(f)d(g|f)
d(f or g) = d(f) + d(g) - d(f and g)
for d(g|f) you do unit/expression propergation of f in g
E.g. d(x' + y | x) = d(y) (I do not have a way to implement it for non single literals on right side of | apart from brute force)
d(g|f) can also be read as what is the density of g in the true area of f.
示例 1:
d(ab'+c)
= d(ab') + d(c) - d(ab'c)
= d(a)d(b) + d(c) - d(a)d(b')d(c)
= 0.5*0.5 + 0.5 - 0.5*0.5*0.5
= 0.625
示例 2:
d((a+b)(a+c))
= d(a+b)d(a+c|a+b)
= (d(a) + d(b) - d(a)*d(b))*(d(a|a+b) + d(c|a+b) - d(ac|a+b))
= 0.75*((2/3) + 0.5 - (1/3))
= 0.625
可能不比蛮力好,但它是递归的。
d(g|f)
如果f
是立方体或文字,也更容易做到。所以下面的身份可以用来交换边:
d(g|f)d(f) = d(f|g)d(g)
另一个使用上述标识的解析示例(用于将 args 交换到给定的运算符)
d((a+b)(a'+b))
= d(a+b)d(a'+b|a+b)
= 0.75(d(a'|a+b) + d(b|a+b) - d(a'b|a+b))
= 0.75(d(a+b|a')d(a')/d(a+b) + d(a+b|b)d(b)/d(a+b) - d(a+b|a'b)d(a'b) / d(a+b))
= 0.75(d(b)d(a') + d(1)d(b) - d(1)*d(a')*d(b))/d(a+b)
= 0.75*(0.5*0.5 + 1*0.5 - 1*0.5*0.5)/0.75
= 0.5
一个有趣的模式发生在 2/3 电路中:
d((a+b)(b+c)(c+a))
= d(a+b)d((b+c)(c+a)|a+b)
. . . skipping many steps . . .
= d((a+b)(b+c)|c)d(c) + d((a+b)(b+c)|a)d(a) - d((a+b)(b+c)|ca)d(ca)
= d(a+b)d(c) + d(b+c)d(a) - d(1)d(c)d(a)
= 0.75*0.5 + 0.75*0.5 - 1*0.5*0.5
= 0.5
还有一个技巧,为了减少分支,可以使用以下恒等式:
d(a+b) = 1 - d(a'b')
这基本上是用于不同目的的德摩根理论。现在,不再分支 3 种方式来处理 OR,而是只分支了一种方式。
再次显示您的示例,显示所有步骤并使用 demorgans 身份:
d(ab'+c')
= 1 - d((ab')'c')
= 1 - d((ab')')d(c'|(ab')')
= 1 - (1 - d(ab'))d((ab')'|c')d(c) / d((ab')')
= 1 - (1 - d(ab'))(1 - d(ab'|c'))d(c') / (1 - d(ab'))
= 1 - (1 - 0.25)(1 - d(ab'))*0.5 / (1 - 0.25)
= 1 - (1 - 0.25)(1 - 0.25)*0.5 / (1 - 0.25)
= 0.625
为常见的子表达式添加缓存也应该提供速度提升。
玩了一会之后。我提出了一个很好的递归表达式,用于以合取范式表示布尔表达式的密度。
let f be the first CNF clause
let g be the remaining clauses
d(fg) = d(g) - d(g|f')d(f')
每个递归在左侧分支中剥离一个子句并从右侧分支中剥离三个变量(对于 3CNF)。(d(f') 是常数时间,因为它是单子句)
这是最后一个表达式在 Rust 中的样子。
pub fn cnf_density(mut cnf: CnfFormula) -> f64 {
if cnf.is_empty() {
return 1.0;
}
// d(fg) = d(g) - d(g|f')d(f')
let clause1 = cnf.remove(0);
let mut cnf2 = cnf.clone();
for lit in &clause1 {
// unit propergation of f' in g|f'
cnf2.assign_lit(lit.negated());
}
let d1 = cnf_density(cnf);
let d2 = cnf_density(cnf2);
d1 - d2 * 2.0_f64.powf(-(clause1.len() as f64))
}
又发现了一个窍门。您可以从旧的密度开始f=1
然后d(f) = 1
计算新的密度,同时添加子句来构建您的 CNF 表达式:
d(f(a+b)) = d(f) - d(f|!a!b)d(!a!b)
如果您希望获得任何性能,您需要对其输入的任何技术进行有限的密度函数缓存。它仍然是指数级的。该d(f(a+b)) = d(f) - d(f|!a!b)d(!a!b)
技术的伟大之处在于它为您提供了一个递减的上限。所以你可以在任何时候终止它,并且仍然告诉你密度会小于什么。如果有一个类似的技巧来增加下限,那就太好了。
Rust 的最后一招:
pub fn cnf_density(&mut self, cnf: CnfFormula, lvl: usize) -> f64 {
if cnf.is_empty() {
return 1.0; }
if cnf.len() == 1 && cnf[0].is_empty() {
return 0.0;
}
if let Some(d) = self.cache.get(&cnf) {
return *d;
} // d(f(a+b)) = d(f) - d(f|!a!b)d(!a!b)
let mut f = 1.0_f64;
let mut cnf2 = CnfFormula::new(); let mut idx: usize = 0;
if self.cache.len() > 1000 {
self.cache.clear();
}
for clause in &cnf {
idx += 1;
if lvl == 0 {
println!("{:?}", clause);
println!("{}/{}", idx, cnf.len());
println!("{}", self.cache.len());
println!("d(f) = {}", f);
}
let mut cnf3 = cnf2.clone();
cnf2.push(clause.clone());
for lit in clause {
cnf3.assign_lit(lit.negated());
}
f -= self.cnf_density(cnf3,lvl+1) * 2.0_f64.powf(-(clause.len() as f64));
}
self.cache.insert(cnf, f);
f
}