数学表达式

本节详细介绍数学表达式的结构,虽然这部分内容比较枯燥,但只有了解表达式的结构,才能随心所欲地对其进行处理,将SymPy运用到更复杂的计算中。

符号

数学符号用Symbol对象表示,符号对象的name属性是符号名,符号名在显示由此符号构成的表达式时使用。Symbol对象和Python的变量没有内在联系,但是为了使用起来方便,通常我们让变量名和符号名相同。为了快速创建符号及其同名的变量,可以使用var(),例如:

>>> var("x0,y0,x1,y1")
(x0, y0, x1, y1)

上面的语句创建了名为x0、y0、x1、y1的四个Symbol对象,同时在当前的环境中创建了四个名为x0、y0、x1、y1的变量分别表示这四个Symbol对象。因为符号对象在转换为字符串时直接使用它的name属性,因此在交互式环境中我们看到变量x0的值就是x0,但是查看变量x0的类型时就可以发现它实际上是一个Symbol对象。

>>> x0
x0
>>> type(x0)
<class 'sympy.core.symbol.Symbol'>
>>> x0.name
x0
>>> type(x0.name)
<type 'str'>

在交互式环境中使用var()能够快速创建变量和Symbol对象,但是在程序中使用它容易引起混淆,这时我们可以使用symbols()创建Symbol对象,再将它们赋值给变量:

>>> x1,y1 = symbols("x1,y1")
>>> type(x1)
<class 'sympy.core.symbol.Symbol'>

当然如果不嫌麻烦也可以直接使用Symbol类创建对象:

>>> x2 = Symbol("x2")
当symbols()的符号名参数中没有逗号或者空格时,它将为每个字符创建一个Symbol对象。例如symbols(“abc”)将创建名称分别为a、b、c的三个Symbol对象。此功能在后续版本中可能会被删除。

变量名和符号名当然也可以是不一样的,例如:

>>> t = x0
>>> t
x0
>>> a,b = symbols("alpha,beta")
>>> a, b
(alpha, beta)

数学公式中的符号一般都有特定的假设,例如m, n通常是整数,而z经常表示复数。在用var()、symbols()或者Symbol()创建Symbol对象时,可以通过关键字参数指定所创建的符号的假设条件,这些假设条件会影响到它们所参与的计算。例如,下面创建了两个整数符号m和n,一个正数符号x:

>>> m, n = symbols("m,n", integer=True)
>>> x = Symbol("x", positive=True)

每个符号都有许多is_*属性,用以判断符号的各种假设条件,在IPython中使用自动完成可以快速查看这些假设的名称,注意下划线后为大写字母的属性用来判断对象的类型,而全小写字母的属性则用来判断符号的假设条件。

>>> x.is_ # 按Tab自动完成
x.is_Add               x.is_bounded           x.is_nonnegative
x.is_Atom              x.is_commutative       x.is_nonpositive
x.is_Derivative        x.is_comparable        x.is_nonzero
x.is_Function          x.is_complex           x.is_number
x.is_Integer           x.is_composite         x.is_odd
x.is_Mul               x.is_even              x.is_polynomial
x.is_Number            x.is_finite            x.is_positive
x.is_NumberSymbol      x.is_hypergeometric    x.is_prime
x.is_Order             x.is_imaginary         x.is_rational
x.is_Piecewise         x.is_infinitesimal     x.is_rational_function
x.is_Pow               x.is_integer           x.is_real
x.is_Rational          x.is_irrational        x.is_unbounded
x.is_Real              x.is_negative          x.is_zero
x.is_Symbol            x.is_noninteger
>>> x.is_Symbol # x是一个符号
True
>>> x.is_positive # x是一个正数
True
>>> x.is_imaginary # 因为x可以比较大小,因此它不是虚数
False
>>> x.is_complex # x是一个复数,因为复数包括实数,而实数包括正数
True

使用assumptions0属性可以快速查看所有的假设条件,其中commutative为True表示此符号满足交换律,其余的假设条件根据英文名很容易知道它们的含义,这里就不再详细叙述了。

>>> x.assumptions0
{commutative: True,
 complex: True,
 imaginary: False,
 negative: False,
 nonnegative: True,
 nonpositive: False,
 nonzero: True,
 positive: True,
 real: True,
 zero: False}

在SymPy中所有的对象都从Basic类继承,实际上这些is_*属性和assumptions0属性都是在Basic类中定义的:

>>> Symbol.mro()
[<class 'sympy.core.symbol.Symbol'>,
 <class 'sympy.core.basic.Atom'>,
 <class 'sympy.core.basic.Basic'>,
 <class 'sympy.core.assumptions.AssumeMeths'>,
 <type'object'>]

数值

为了实现符号运算,在SymPy内部有一整套数值运算系统。因此SymPy的数值和Python的整数、浮点数是完全不同的对象。为了使用方便,SymPy会尽量自动将Python的数值类型转换为SymPy的数值类型。此外SymPy提供了一个S对象进行这种转换。在下面的例子中,当有SymPy的数值参与计算时,结果也为SymPy的数值对象。

>>> 1/2 + 1/3 # 结果为浮点数
0.833333333333
>>> S(1)/2 + 1/S(3) # 结果为SymPy的数值对象
5/6

“5/6”在SymPy中使用Rational对象表示,它由两个整数的商表示,数学上称之为有理数。也可以直接通过Rational创建:

>>> Rational(5, 10) # 有理数会自动进行约分处理
1/2

实数用Real对象表示,它和标准的浮点数类似,但是它的精度(有效数字)可以通过参数指定。由于在浮点数和Real对象内部都使用二进制的方式表示数值,因此它们都无法精确表示十进制中的精确小数,例如0.1。我们可以使用N()查看浮点数的实际数值,例如下面的语句查看浮点数0.1和10000.1的60位有效数字,可以看到数值的绝对精度随着数值的增大而减小:

>>> N(0.1, 60)
0.100000000000000005551115123125782702118158340454101562500000
>>> N(10000.1, 60)
10000.1000000000003637978807091712951660156250000000000000000

因为浮点数的精度有限,因此使用它创建Real对象时,即使指定精度参数也不能缩小它与理想值之间的误差,这时我们可以使用字符串表示数值:

>>> N(Real(0.1,60),60) #用浮点数创建Real对象时,精度和浮点数相同
0.100000000000000005551115123125782702118158340454101562500000
>>> N(Real("0.1",60),60) #用字符串创建Real对象时,所指定的精度有效
0.100000000000000000000000000000000000000000000000000000000000
>>> N(Real("0.1",60),65) #提高表示精度,就会发现它也不是完全精确的
0.099999999999999999999999999999999999999999999999999999999999996111

运算符和函数

SymPy重新定义了所有的数学运算符和数学函数。例如Add类表示加法,Mul类表示乘法,而Pow类表示指数运算,sin类表示正弦函数。和Symbol对象一样,这些运算符和函数都从Basic类继承,请读者自行在IPython中查看它们的继承列表(例如:Add.mro())。我们可以使用这些类创建复杂的表达式:

>>> var("x,y,z,n")
>>> Add(x,y,z)
x + y + z
>>> Add(Mul(x,y,z), Pow(x,y), sin(z))
x*y*z + sin(z) + x**y

由于在Basic类中重新定义了__add__()等用于创建表达式的方法,因此可以使用和Python表达式相同的方式创建SymPy的表达式:

>>> x*y*z + sin(z) + x**y
x*y*z + sin(z) + x**y

在Basic类中定义了两个很重要的属性:func和args。func属性得到对象的类,而args得到其参数。使用这两个属性我们可以观察SymPy所创建的表达式。也许读者会对没有减法运算类感到奇怪,下面让我们看看减法运算所得到的表达式:

>>> t = x - y
>>> t.func # 减法运算用加法类Add表示
<class 'sympy.core.add.Add'>
>>> t.args # 两个加数一个是x,一个是-y
(x, -y)
>>> t.args[1].func # -y是用Mul表示的
<class 'sympy.core.mul.Mul'>
>>> t.args[1].args
(-1, y)

通过上面的例子可以看出,表达式“x-y”在SymPy中实际上是用“Add(x, Mul(-1, y))”表示的。同样,SymPy中没有除法类,请读者使用和上面相同的方法观察“x/y”在SymPy中是如何表示的。

SymPy的表达式实际上是一个由Basic类的各种对象进行多层嵌套所得到的树状结构。下面的函数使用递归显示这种树状结构:

04-sympy/print_expression.py

显示SymPy的表达式

def print_expression(e, level=0):
    spaces = "    "*level
    if isinstance(e, (Symbol, Number)):
        print spaces + str(e)
        return
    if len(e.args) > 0:
        print spaces + e.func.__name__
        for arg in e.args:
            print_expression(arg, level+1)
    else:
        print spaces + e.func.__name__

例如\sqrt{x^2+y^2}在SymPy中使用下面的树表示:

>>> print_expression(sqrt(x**2+y**2))
Pow
    Add
        Pow
            y
            2
        Pow
            x
            2
    1/2

由于其中的各个对象的args属性类型是元组,因此表达式一旦创建就不能再改变。使用不可变的结构表示表达式有很多优点,例如我们可以用表达式作为字典的键。

除了使用SymPy中预先定义好的具有特殊运算含义的数学函数之外,还可以使用Function()创建自定义的数学函数:

>>> f = Function("f")

请注意Function虽然是一个类,但是上面的语句所得到的f并不是Function类的实例。和预定义的数学函数一样,f是一个类,它从Function类继承:

>>> f.__base__
Function
>>> isinstance(f, Function)
False

在Python中,类型(类)也是对象,通常类型对象的类型是type对象,通过type()可以获得对象的类型,因此:

>>> type([1,2,3]) # 列表对象的类型是列表
<type 'list'>
>>> type(list) # 列表类型的类型是type
<type 'type'>

但是通过Function创建的类型对象f的类型并不是type:

>>> type(f)
<class 'sympy.core.function.FunctionClass'>

当我们使用f创建一个表达式时,就相当于创建它的一个实例:

>>> t = f(x,y)
>>> type(t)
f
>>> t.func
f
>>> t.args
(x, y)

f的实例t可以参与表达式运算:

>>> t+t*t

(1)f\left(x,y\right) + {f}^{2}\left(x,y\right)

內容目录

上一个主题

从例子开始

下一个主题

符号运算

本页

loading...