失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 求导的符号运算

求导的符号运算

时间:2021-05-21 16:15:36

相关推荐

求导的符号运算

求导的符号运算

这是最近做的一个python的小项目,内容是实现求导的符号运算,虽然我最终的实现化简能力能力比较弱,但是还是一个很有意思的项目。

语法树的建立

语法树的结构

语法树是我这个项目里面所有表达式的存在形式。

树的一个结点要么是字符串(常数或者变量),要么是内部结点,内部结点代表一棵子树,对应存储的结构体有两个字段:根存储的运算和子结点的列表,列表元素个数是操作符的元数,列表元素是子结点。

例如前缀表达式(+ (* x 2) 1)表示2x+1,那么对应的树根结点是’+’,对应的子结点列表是[tree(2x), 1],其中tree(2x)又是一棵子语法树,根为’*’,对应子结点的列表是[2, x]。即语法树是递归定义的,最终叶子结点是常量或者变量。

前缀表达式转化为语法树

我的项目中约定输入前缀表达式,即类似lisp的表达式,所以转化非常方便。

对于前缀表达式(op arg0, arg1, …, argn),先读入op为根结点操作符,然后读入参数列表即可建立语法树,注意读入参数列表过程中可能读到表达式,需要递归建树。

语法树的运算

求值

利用前缀表达式的算法递归求值即可。遇到变量,代入具体数值即可。

设calculate(tree,x)计算tree在变量取值x时候的值。

求函数

因为要求一个函数而不是具体数值,所以需要返回一个函数或者lambda表达式,可以实现func(tree) 为lambda x: calculate(tree, x)。

求导

这里是算法的核心。

对于二元运算,算法为递归求导,规则如下:

1.叶子结点:x导数为1,常数导数为0。

2.非叶子结点,左右子树为l,r。那么结点的运算来递归求导:

(l+r)′=l′+r′

(l−r)′=l′−r′

(l∗r)′=l∗r′+l′∗r

(l/r)′=(l′∗r−l∗r′)/(r2)

(lr)′=(rlr−1)∗l′+ln(l)lr∗r′

手动实现这五条规则即可。

对于一元函数,有统一的计算方法,即链式法则:

h(g(x))′=h′(g(x))∗g′(x)

所以需要保存一个函数表,保存下每一个函数的导函数,利用链式法则递归计算即可。注意我们这儿并不保存导函数的函数形式,而是保存如何建立导函数语法树的lambda表达式。、

例如,ln(x)的导函数就是lambda x: ExpTree(‘\’, [1, x])。

语法树化简

因为上述的求导规则经常会产生0或者1项,例如:

(2∗x)′=(2)′∗x+2∗(x)′=0∗x+2∗1

所以需要化简,规则为:

1. 0+M=M+0=M

2. M−0=M

3. 1∗M=M∗1=M

4. 0∗M=M∗0=0

5. M/1=M

6. 0/M=0

7. M1=M

8. 0M=0

手动实现规则即可。

要注意的是,需要先化简子树来递归化简。另外,如果左右子树都是常数,可以直接计算。

界面和交互

界面需要用户输入前缀表达式和运算指令,输出对应的函数或者函数值,另外输出对应函数的时候,为了直观,除了函数的中缀表达式,我还输出了函数图像。

交互上我没有花和大力气,所以格式相对比较死板,不过提供了足够的帮助信息,应该用起来很方便。

还需要改进的地方

但是,这毕竟只是一个耗时一天不到的小项目,要改进的地方还有很多:

1.这个项目不能运用运算律和函数性质化简,所以以下这些化简,暂时无法完成:

x-x=0,2x+3x=5x,sinx * sinx + cosx * cosx=1,ln(exp(x))=x。

2.这个项目暂时不支持自己指定中间函数,这个涉及到词法语法分析等等,没有实现。

3.对于重复表达式没有共享而是一一计算,没有优化。

但是毕竟这是一个轻量级项目,所以不准备加入这些特性了。

如果觉得《求导的符号运算》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。