Algorithm 布尔函数密度的计算算法

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个最小项,但其中一个最小项被两个立方体覆盖 有人能想出一个好的算法来处理这个问题吗?我强烈怀疑它会以递归的方式得到最好的

我正试图写一个程序,需要计算一个特定的值来处理布尔函数。给定一个单输出布尔函数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
    要么为
    false
    。对于BDD中的每个路径
    • 如果叶节点为
      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
    }