F#库FParsec实现求导符号计算

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 利用F#实现对用户输入一个文本类型的数学公式进行解析,且自动解析成Expr类型,然后根据求导规则进行符号求导计算。最终实现控制台输入sin(2*x) 求导系统即可自动计算出(cos((2*x))*2)。

   在之前的博客中,我已经介绍了一下如何用F#对数学表达式进行求导,但是其中有一个问题,就是求导的对象是一个Expr类型,这样使用起来是很不方便的。一般来说,用户使用都是直接输入一个文本类型的数学公式,然后求导系统应该就可以对其进行自动解析成Expr类型,并根据求导规则进行求导的符号计算。

   如果对之前撰写的博文【F#表达式求导】,感兴趣的话可以自行去阅读。下面,给出利用F#解析器库FParsec实现公式求导符号计算。

1 文本解析到Expr

首先,在F#项目中引入FParsec库,并定义一个Expr类型:

typeExpr=|CstFoffloat|Varofstring|AddofExpr*Expr// +|SubofExpr*Expr// -|MulofExpr*Expr// *|DivofExpr*Expr// / |PowofExpr*Expr// ^ |SinofExpr|CosofExpr|ExpofExpr|LnofExpr|NegofExpr|FactorialofExpr|Errorofstring

       这个类型中包含了变量Var,数值CstF,加减乘除运算符,三角函数等。其次,再利用FParsec库提供的强大解析功能,给出一些解析工具方法。这些方法有的可以解析浮点类型,有的可以解析从小括号中提取表达式,具体示例如下:

//忽略空白字符letprivatews=CharParsers.spaces//忽略特定字符并忽略末尾的空白字符letprivatechc=CharParsers.skipCharc>>. ws//解析浮点类型的值,忽略末尾的空白字符,并转换成 CstF 类型letprivatenum=CharParsers.pfloat .>>ws|>> (funx->CstFx)
//变量标识符规则letprivateidentifier=letisIdentifierFirstCharc=isLetterc||c='_'letisIdentifierCharc=isLetterc||isDigitc||c='_'many1Satisfy2LisIdentifierFirstCharisIdentifierChar"identifier"//忽略空白letprivateidentifierws=spaces>>.  identifier  .>>spaces//变量解析,注意|>>与id对其,否则报错letprivateid=identifierws|>>(funx->Varx)
                   .>>ws//创建一个新的具有优先级的操作符解析器letprivateopp=newOperatorPrecedenceParser<_,_,_>()
//重命名表达式解析器ExpressionParser,方便调用letprivateexpr=opp.ExpressionParser//括号中提取表达式letprivatebra_expr=ch'('>>. expr .>>ch')'// 定义支持的术语terms,即操作符外的类型解析器//id表示变量解析器,num表示浮点类型解析器,bra_expr表示表达式解析器letprivateterms=choice[ id; num; bra_expr]
opp.TermParser<-termsopp.AddOperator(InfixOperator("+", ws,1, Associativity.Left, funxy->Add(x, y)))
opp.AddOperator(InfixOperator("-", ws,1, Associativity.Left, funxy->Sub(x, y)))
opp.AddOperator(InfixOperator("*", ws,2, Associativity.Left, funxy->Mul(x, y)))
opp.AddOperator(InfixOperator("/", ws,2, Associativity.Left, funxy->Div(x, y)))
opp.AddOperator(InfixOperator("^", ws,3, Associativity.Left, funxy->Pow(x, y)))
opp.AddOperator(PrefixOperator("sin", ws,4, true, funx->Sin(x)))
opp.AddOperator(PrefixOperator("cos", ws,4, true, funx->Cos(x)))
opp.AddOperator(PrefixOperator("exp", ws,4, true, funx->Exp(x)))
opp.AddOperator(PrefixOperator("ln", ws,4, true, funx->Ln(x)))
opp.AddOperator(PostfixOperator("!", ws,5, true, funx->Factorial(x)))
//忽略空白letprivateexpr_ws=ws>>. expr .>>ws//调用run方法调用字符解析器letprivateparses=CharParsers.runexpr_wss

     这里需要注意一下,不少方法定义的时候的有关键字private限定,这表示私有的,在模块内可以访问,但在模块外无法直接访问。下面再定义一个公有方法,可以在其他模块进行访问。具体示例如下:

letstrParsers=matchparseswith|Success(value, _, _)   ->value|Failure(err, _, _) ->Errorerr

    一般来说,一个函数返回的类型是一致的,由于parse解析文本后,返回的类型可以是成功的Success,也可以是失败的Failure,为了兼容考虑,这里定义了一个Error 类型,它也是一个Expr类型。而Success返回的value就是一个Expr类型。

2 Expr求导计算

     下面,给出Expr类型的求导函数,具体的示例如下:

letrecprivatediffe=matchewith|CstFf->CstF0.0|Varx->CstF1.0|Add(CstFa, Varx)   ->CstF1.0|Add(e1, e2)  ->Add(diffe1, diffe2)
|Sub(e1, e2)  ->Sub(diffe1, diffe2)
|Mul(CstFa, Varx) ->CstFa|Mul(e1, e2) ->Mul(diffe1, diffe2)
|Pow(Varx,CstFa) ->Mul(CstFa,Pow(Varx,CstF (a-1.)))
|Pow(e1,e2)  ->Mul(e2,Pow(e1, Sub(e2,CstF1.)))
|Sin(e1)  ->Mul(Cos(e1),diffe1)
|Cos(e1)  ->Mul(Neg(Sin(e1)),diffe1)
|Neg(e1)  ->Neg(diffe1)
|e->eletrecprivateprintExpre=matchewith|CstFf->stringf|Varx->x|Add(e1 , e2) ->"("+ (printExpre1) +"+"+ (printExpre2) +")"|Sub(e1 , e2) ->"("+ (printExpre1) +"-"+ (printExpre2) +")"|Mul(e1 , e2) ->"("+ (printExpre1) +"*"+ (printExpre2) +")"|Div(e1 , e2) ->"("+ (printExpre1) +"/"+ (printExpre2) +")"|Pow(e1 , e2) ->"("+ (printExpre1) +"^"+ (printExpre2) +")"|Sin(e1) ->"sin("+ (printExpre1) +")"|Cos(e1) ->"cos("+ (printExpre1) +")"|Neg(e1) ->"-("+ (printExpre1) +")"|_->failwith"printExpr error"letdiff1s=diff (strParsers) |>printExpr

  其中的diff是一个private内部方法,且用rec关键字限定说明是一个递归函数。这里需要注意一下,rec在private关键字之前,而不能调换位置。同理,printExpr函数则是将Expr类型的表达式输出为文本类型的数学公式。

    最后,定义一个diff1 函数,它接受1个文本类型的输入,首先通过strParser s 解析成Expr类型的对象,然后调用diff函数进行公式求导,并将求导后的结果作为参数传递给函数printExpr进行打印输出。

3 求导测试

     最后,给出求导公式的测试程序:

openSystemopenYd.ExpParser[<EntryPoint>]
letmainargv=Console.WriteLine"Welcome YdCAS Demo【JackWangCUMT】";
Console.WriteLine"目前只支持求导:sin(2*x) - > (cos((2*x))*2)";
Console.Write"$>";
letmutableinput=Console.ReadLine()
whileinput<>"quit"doprintfn"diff(%s) => %O"input (diff1input)
Console.Write"$>";
//input = Console.ReadLine() //不能修改初始值input<-Console.ReadLine() //可以printfn"%s"input0

  运行并输入如下测试用例,结果为:

WelcomeYdCASDemo【JackWangCUMT】目前只支持求导:sin(2*x) -> (cos((2*x))*2)
$>x^3diff(x^3) => (3*(x^2))
$>sin(x^2+3*x)
diff(sin(x^2+3*x)) => (cos(((x^2)+(3*x)))*((2*(x^1))+3))
$>x^2+3*xdiff(x^2+3*x) => ((2*(x^1))+3)
$>cos(x)
diff(cos(x)) => (-(sin(x))*1)
$>


相关文章
|
XML 并行计算 算法
[Eigen中文文档] 求解稀疏线性系统
在Eigen中,有多种方法可用于求解稀疏系数矩阵的线性系统。由于此类矩阵的特殊表示,必须特别小心以获得良好的性能。本文列出了Eigen中可用的稀疏求解器。还介绍了所有这些线性求解器共同的主要步骤。根据矩阵的属性、所需的准确度,最终用户可以调整这些步骤以提高其代码的性能。请注意,并不需要深入了解这些步骤背后的内容:最后一节介绍了一个基础例程,可轻松使用以获取所有可用求解器的性能洞察。
335 0
|
4月前
|
存储 Serverless Python
`quad()` 函数是 `scipy.integrate` 模块中的一个函数,用于计算一维函数的数值积分。其基本语法如下:
`quad()` 函数是 `scipy.integrate` 模块中的一个函数,用于计算一维函数的数值积分。其基本语法如下:
|
5月前
|
算法 Python
使用Python和Gurobi求解无约束优化问题
使用Python和Gurobi求解无约束优化问题
119 0
|
6月前
|
机器学习/深度学习 搜索推荐 数据挖掘
矩阵运算与分解:NumPy的高级应用
【4月更文挑战第17天】NumPy是Python数值计算基础库,支持高效矩阵运算和分解。本文介绍了NumPy的矩阵加减、乘法以及特征值、奇异值和Cholesky分解,并展示了它们在机器学习(如线性回归、PCA)、图像处理和科学计算中的应用。通过掌握这些高级功能,用户能更有效地处理矩阵数据,解决各种计算和分析任务。
|
6月前
|
机器学习/深度学习 算法 搜索推荐
SciPy线性代数库详解:矩阵运算与方程求解
【4月更文挑战第17天】SciPy的`scipy.linalg`模块提供丰富的线性代数功能,包括矩阵运算、线性方程组求解、特征值问题和奇异值分解等,基于BLAS和LAPACK库确保效率与稳定性。关键操作如矩阵乘法使用`dot`函数,转置和共轭转置用`transpose`和`conj`,求解线性方程组有`solve`和迭代方法,计算特征值和向量用`eig`,奇异值分解则依赖`svd`。这个库对科学计算、数据分析和机器学习等领域至关重要。
|
6月前
|
数据可视化
R语言最优化问题中的共轭函数
R语言最优化问题中的共轭函数
|
6月前
|
算法
MATLAB | 插值算法 | 一维Lagrange插值法 | 附数据和出图代码 | 直接上手
MATLAB | 插值算法 | 一维Lagrange插值法 | 附数据和出图代码 | 直接上手
192 0
|
6月前
|
机器学习/深度学习 PyTorch 算法框架/工具
(python)利用pytorch拟合法求解非线性方程组
(python)利用pytorch拟合法求解非线性方程组
226 0
|
测试技术
[Eigen中文文档] 线性代数与分解
本节将说明如何求解线性系统,计算各种分解,如 LU、QR、SVD、特征分解……
223 0
|
Python
分段模型线性化(PWL)【Python|Gurobi实现】
分段模型线性化(PWL)【Python|Gurobi实现】
559 0