简化数学表达式中的嵌套乘法

时间:2014-12-25 22:52:21

标签: f# symbolic-math

我有以下联合作为符号数学表达式的AST:

type Expr =
| Const of float
| X of float * float
| Sum of Expr * Expr
| Mul of Expr * Expr
| Pow of Expr * Expr
| Sin of Expr
| Cos of Expr

static member (+) (e1,e2) = Sum(e1,e2)
static member (*) (e1,e2) = Mul(e1,e2)
static member (/) (e1,e2) = Mul(e1, Pow(e2,Const -1.0))

let (!) e = Mul(Const -1.0,e)
let (^) e n = Pow(e ,n)

问题:

我正在尝试简化Mul(Mul(expr1,expr2),expr3)Mul(e,Mul(e1,e2))之类的表达式,或者通常在表达式中存在n次嵌套乘法时。

我写了这个simp函数,它接受Expr并递归地简化了事情。 (只是一些代数),当从simp

调用函数fullSimp时,此函数的两个规则(读取代码中的注释)会导致无限递归
let rec simp expr =
    match expr with
    | Mul(Sum(e1,e2),e) -> simp (e1*e) + simp (e2*e)
    | Mul(e,Sum(e1,e2)) -> simp (e1*e) + simp (e2*e)
    | Mul(e1,e2) when e1 = e2 -> simp e1 ^ Const 2.0
    | Mul(Mul(e1,e2),e) -> Mul(simp (e1*e),simp e2) // adding this rule makes the function 'fullSimp' recurse infinitely
    | Mul(e,Mul(e1,e2)) -> Mul(simp (e2*e), simp e1) // same as above
    | Mul(e1,e2) -> simp e1 * simp e2
    | Cos e -> Cos(simp e)
    | Sin e -> Sin(simp e)
    | _ -> expr

然后有一个函数fullSimp调用simp,直到simp没有返回表达式的简化版本

let rec fullSimp e = 
    let simplified = simp e
    if simplified <> e then fullSimp simplified
    else simplified

尝试评估以下表达式会产生无限递归!!!

fullSimp (Mul (Mul (Const 2.0,Cos (X (1.0,1.0))),Sin (X (1.0,1.0)))) 
// notice the nested multiplications here
// also notice that this does not simplify any furthur so the function 
// should return the same input.

simp中删除两个规则解决了无限递归问题,但没有简化嵌套乘法的问题。

编辑:为了区别对待问题,我试图收集(乘)嵌套多重复制节点中的常量和变量。例如,我试图采用像

这样的表达式

Mul(Const 5.0,Mul(Mul (Const 2.0,Sin (X (4.0,2.0))),Mul (Cos (X (4.0,2.0)),X (8.0,1.0))))

并将其转换为Mul (Mul (X (80.0,1.0),Sin (X (4.0,2.0))),Cos (X (4.0,2.0))),这无法简化任何更进一步的

注意根据评论中的反馈,我编辑了几次。现在希望很清楚问题是什么。对于不一致的道歉。

2 个答案:

答案 0 :(得分:1)

伙计,你创造了一个怪物。无论你想要做什么,必须有一个更好的方式来模拟它比40个案例的长匹配。

这是我将您的简化版本提炼为:

let rec simp expr =
    match expr with
    | Mul(Mul(e1,e2),e) -> Mul(simp (e1*e), simp e2)
    | _ -> expr

当你在测试表达式上尝试它时,你会发现它只是在两种状态之间振荡:

[Mul (Mul (Const 2.0,Sin (X (1.0,1.0))),Cos (X (1.0,1.0)));
 Mul (Mul (Const 2.0,Cos (X (1.0,1.0))),Sin (X (1.0,1.0)));
 Mul (Mul (Const 2.0,Sin (X (1.0,1.0))),Cos (X (1.0,1.0)));
 Mul (Mul (Const 2.0,Cos (X (1.0,1.0))),Sin (X (1.0,1.0))); ...]

这真的不足为奇,因为所有这些情况都是切换ee2表达式(由于您重载(*)而进一步模糊)。这种情况只是一个盒子里的无限循环。

你可能想要的是某种回溯算法,一旦检测到循环就会退出这种循环场景。第一次将这样的东西放在适当的位置就是积累所有以前访问过的表达式,如果它让你进入你已经看过的状态,就跳过这个案例。

答案 1 :(得分:0)

我想我找到了我想要的东西:

逃避inifinte递归,或者实际上不首先输入它是修改fullSimp函数,因为它只检查两个表达式是否等于#34;所以现在它也检查在简化之后节点数是否已经改变,所以我添加了这个长度函数:

let rec length expr = 
    match expr with
    | Const _
    | X(_,_) -> 1
    | Sum(e1,e2)
    | Mul(e1,e2) 
    | Pow(e1,e2) -> 1 + (length e1) + length (e2)
    | Sin(e) | Cos(e) | Exp(e) | Log(e) | Sinh(e) | Cosh(e) -> 1 + (length e)

let notSameLength e1 e2 = 
    length e1 <> length e2

然后我用这些模式取代了25+模式,看着gaurds,只有当它变得有意义时才会改变乘法顺序#34;即当事情 CAN 简化了时间: (我知道,我在这里两次致电simp,尚未完全优化)

| Mul(Mul(e1,e2),e) 
| Mul(e,Mul(e1,e2)) when notSameLength (e1*e) (simp (e1*e)) -> Mul(simp (e1*e),simp e2) 
| Mul(Mul(e1,e2),e) 
| Mul(e,Mul(e1,e2)) when notSameLength (e2*e) (simp (e2*e)) -> Mul(simp (e2*e),simp e1)

最后fullSimp成为:

let rec fullSimp e = 
    let simplified = simp e
    if simplified <> e && (length simplified <> length e) then fullSimp simplified
    else simplified

到目前为止我尝试过的每个例子确实很简单,所以我认为这是解决方案。