【问题标题】:Simplifying nested multiplications in a math expression简化数学表达式中的嵌套乘法
【发布时间】:2014-12-25 22:52:21
【问题描述】:

我有以下联合作为符号数学表达式的 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 并递归地简化事情。 (只是一些代数),当从fullSimp 调用函数simp 时,这个函数的两个规则(阅读代码中的cmets)导致无限递归

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))) 不能再简化了

注意根据 cmets 中的反馈,我编辑了几次。现在希望很清楚问题是什么。对不一致之处深表歉意。

【问题讨论】:

  • 我知道,这将是问题的重现。但是您发布的代码不完整,无法编译。
  • 好的,你知道那些静态成员,但我们不知道,因为你没有在代码中指定它们。这就是重点,如果您发布的代码不完整,我们将面临并非真正问题的错误。您发布的内容应该是最小的并且同时是完整的,这意味着如果它是在 fsi 会话中复制粘贴执行的,它应该编译(或者如果这是您的问题,最终会引发编译器错误)。
  • @ZaidAjaj - 请阅读本指南:stackoverflow.com/help/mcve
  • 作为一个问题的答案:如果你删除除了有问题的规则之外的每一条规则会发生什么?来自simp
  • 这是一个提示 - 一旦你明白为什么会发生这种情况,你就会明白为什么原始版本会崩溃

标签: f# symbolic-math


【解决方案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 表达式之间切换(这会被您重载 (*) 的事实进一步掩盖)。这种情况只是一个盒子里的无限循环。

您可能想要的是某种回溯算法,一旦它检测到循环就会退出这种循环场景。放置类似内容的第一个技巧是累积所有先前访问过的表达式,如果它使您进入您已经看到的状态,则跳过该案例。

【讨论】:

  • 我现在添加了一个length 函数来检查节点的数量,因为振荡表达式在数学上是相同的,仅检查对象是否相等是不够的。所以现在使用length 可以检测到简化表达式的变化。
  • 我已经有了“解决方案”,但还是感谢您的回答
【解决方案2】:

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

逃避无限递归,或者实际上不进入它是修改fullSimp函数,因为它只检查两个表达式是否“相等”,所以现在它也检查个数节点在简化后发生了变化,所以我添加了这个长度函数:

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+ 模式,看看守卫,它只会在“有意义”时改变乘法顺序,即当事情 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

到目前为止,我尝试的每个示例都很好地简化了,所以我认为这是解决方案。

【讨论】:

  • 只有在每个转换步骤减少节点数的情况下,这个“才有效”。对于您感兴趣的解决方案子集,这可能没问题,但这是一个大胆的声明。直观地说,你正在用它切断大部分搜索空间。
  • @scrwtp 你是对的,这可能不适用于某些示例,但我想不出一个不“有效”的示例。但是,这只是一个重写操作,并且仅用于漂亮打印的目的,表达式保持不变(数学上),因此任何进一步的操作都不会受到影响。
猜你喜欢
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多