Algorithm 布尔函数密度的计算算法
我正试图写一个程序,需要计算一个特定的值来处理布尔函数。给定一个单输出布尔函数f,由一个覆盖函数f给出,假设我将函数的密度定义为所有输入向量的分数,其中函数的值为1 例如,假设我传入给定函数f(a,b,c),该函数由覆盖f=ab'+c'定义。该函数有5个固定最小项和8个总最小项,因此其密度为d(f)=5/8=0.625。应该注意的是,立方体ab'覆盖2个最小项,立方体c'覆盖4个最小项,但其中一个最小项被两个立方体覆盖Algorithm 布尔函数密度的计算算法,algorithm,boolean,boolean-logic,boolean-expression,cad,Algorithm,Boolean,Boolean Logic,Boolean Expression,Cad,我正试图写一个程序,需要计算一个特定的值来处理布尔函数。给定一个单输出布尔函数f,由一个覆盖函数f给出,假设我将函数的密度定义为所有输入向量的分数,其中函数的值为1 例如,假设我传入给定函数f(a,b,c),该函数由覆盖f=ab'+c'定义。该函数有5个固定最小项和8个总最小项,因此其密度为d(f)=5/8=0.625。应该注意的是,立方体ab'覆盖2个最小项,立方体c'覆盖4个最小项,但其中一个最小项被两个立方体覆盖 有人能想出一个好的算法来处理这个问题吗?我强烈怀疑它会以递归的方式得到最好的
有人能想出一个好的算法来处理这个问题吗?我强烈怀疑它会以递归的方式得到最好的表达,但我很难找到有效的方法。坏消息是:永远快速的算法是没有希望的 即这个问题: 给定一个合取范式(和的乘积)的布尔公式,确定是否有自由变量的赋值,以使公式产生真值 是NP完全的。这意味着,如果你找到一个多项式时间算法来解决它,你也可以在多项式时间内解决一些世界上最难的问题(背包问题、旅行推销员问题、哈密顿循环问题等等)。没有人真的期望这是可能的 这个问题实际上相当于这个问题: 给定一个析取范式(乘积之和)的布尔公式,确定其密度是否为100%
好消息是: 输入大小可能非常小。在三个变量中,你并不真正关心速度。对于30个输入变量,您仍然更有可能耗尽内存(使用某些算法),而不是运行得太长 算法#1:
O(2^v*i)
时间,几行代码,v
=变量数<代码>i=输入的长度
- 如果任何子句不一致(
),请将其删除A&!A
- 首先按大小、最大值(变量最少)对子句进行排序
- 对于通用集合中的每个最小项
- 对于输入中的每个子句
- 对于术语中的每个文字
- 如果minterm未包含在文本中,请继续下一个子句
- 最低条款包含在以下条款中:
- 将最小项计算为已覆盖,然后继续计算下一个最小项
- 对于术语中的每个文字
- minterm不包含在任何文字中;继续下一分钟
- 对于输入中的每个子句
- 让
density=[#涵盖条款]/[#条款]
如果你想跑得更快,你需要一个好的输入。您可以尝试使用二进制决策图(BDD)对当前已编码的最小项集进行编码,并在从输入中添加子句时尝试更新二进制决策图 二元决策图是一个有根有向无环图,这样每个节点要么是决策节点(测试单个变量,然后取假分支或真分支),要么是叶节点(要么
true
要么false
)。例如,XOR
可以用以下二进制决策图表示:
|
A
/ \
/ \
B B
/ \ / \
0 1 1 0
算法#2(延迟扩展BDD,更复杂,但对于大量变量可能更快):
- 如果任何子句不一致(
),请将其删除A&!A
- 首先按大小、最大值(变量最少)对子句进行排序
- 以空BDD开始(root=false)
- 每项条款
- 更新根目录上的BDD:start
- 对于每个变量:
- 如果该子句没有更多的文本,请将当前节点替换为
(仅在您所处的边缘处)true
- 如果直接同级也是
,则将公共父级替换为true
,并对新节点重复此测试true
- 如果直接同级也是
- 如果当前节点为
true
- 继续下一个子句或递归分支,或加入同级线程
- 如果变量位于子句和当前BDD节点中,
- 下到与该子句相交的儿童处
- 如果变量在子句中,但不在当前BDD节点中。
- 创建新的BDD节点
- 将两个子节点都设置为当前节点
- 用新节点替换当前节点(仅在沿其移动的边缘处)
- 下到与该子句相交的儿童处
- 如果变量在BDD中,但不在子句中
- 依次递减到两个孩子身上。或者,以单独的线程并行下降到两个子级
- 如果变量既不在BDD中,也不在子句中
- 什么也不做
- 如果该子句没有更多的文本,请将当前节点替换为
- 让密度=0/(2^变量)(分母表示整数计算的建议比例)
- 每个叶节点要么为
要么为true
。对于BDD中的每个路径false
- 如果叶节点为
true
- 让
为沿路径遇到的非叶节点数length
- 将
添加到1/(2^长度)
密度中
- 让
- 如果叶节点为
设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
也许不比暴力好,但它是递归的
如果f
是一个立方体或一个文本,那么执行d(g | f)
也更容易。因此,可以使用以下标识交换双方:
d(g|f)d(f) = d(f|g)d(g)
使用上述标识进行解析的另一个示例(用于将参数交换到给定的运算符)
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')
T
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(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')
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))
}
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
}