跳至內容

Mathematica/基本理念

維基教科書,自由的教學讀本

引言

[編輯]

龐大而複雜的Mathematica系統是在少數幾條基本理念或者說原則上建立起來的。了解一下這些基本理念對後面的內容有幫助,下面做簡要介紹。

萬物皆為表達式

[編輯]

Mathematica中的所有東西都被當作表達式(expression)看待,表達式可以粗略地分為兩種:原子(atom)和普通表達式(nomal expression)。

原子

[編輯]

原子顧名思義就是組成表達式的最小成分,原子可按照以下方法分類:

  • 原子
    • 數字(numbers)
      • Real(實數,如3.22)
      • Rational(分數,如Rational[2,3])
      • Complex(複數,如1+2I)
    • Symbol(符號,如Pi,Plot,a,b)
    • String(字符串,如"Mathematica is a new way of thinking!")

在現實世界中,原子構築起大千世界;在Mathematica中,原子組成所有的普通表達式。內置函數AtomQ可以用來測試一個表達式是否為原子:

 In[1]:=	{AtomQ[1], AtomQ[a], AtomQ[1 + a], AtomQ["love"], AtomQ[1/3], AtomQ[Cos[2]]}
Out[1]=	{True, True, False, True, True, False}

普通表達式

[編輯]

上面提到,普通表達式是由原子構成的,構成的一般格式是:expr[expr1,expr2,...,exprn]

  • <expr>稱為頭部(head),它本身可以是一個普通表達式而不一定是原子[1]
  • 頭部後面跟上一對方括號,可以姑且理解為其它編程語言中的函數參數的標識,比如C語言中的小括號。
  • < expr1,expr2,...,exprn >是此表達式的成員(elements,可以理解為C中的函數參數),成員的個數可以為任意個,包括0個。

表達式的頭部成員本身可以是不可再細分的原子,也可以是由相同的格式構成的子表達式。可以把一個普通表達式看成樹一樣的結構,樹葉(原子)構成樹枝(子表達式),樹枝再構成樹幹[2]

內置函數的等價形式 FullForm命令

[編輯]

上面的提到的頭部加元素這種構成格式是Mathematica的通用表示方法,內核中的計算都是按照這種形式進行的。但是為了方便用戶的輸入和閱讀,對於一些常用的函數,Mathematica提供了較為簡潔等價形式。例如1加1的完全形式是Plus[1,1],這真是麻煩啊,直接寫1+1就可以了。對於常見的數學運算,基本上都可以用標準的數學符號簡寫,+、-、*、/、^等等。 有時需要查看一個表達式的完全形式,這就要用到函數FullForm:

 In[2]:=	FullForm[x*Sin[a+b]]
Out[2]=	Times[x,Sin[Plus[a, b]]]

表達式的索引

[編輯]

可以使用索引(Part命令)來訪問一個表達式的內部(方括號裡面的部分)。下面舉例:

 In[3]:=	expr=x*Sin[a+b];{expr[[0]],expr[[1]], expr[[2]], expr[[2, 1]], expr[[2, 1, 1]]}
Out[3]=	{Times, x, Sin[a + b], a + b, a}

可以看到,索引值0代表表達式的頭部(f[[0]]相當於Head[f]),索引值1、2、3分別代表表達式的第1、2、3個元素。逗號可以用來進行多層訪問。

用這種方法我們可以對任意表達式進行重組和改變。

表達式的層次 Level命令

[編輯]

用Level命令可以得到特定層次的子表達式:

 In[4]:=	Level[expr, {3}]
Out[4]=	{a,b}

可用TreeForm查看expr的樹形結構以直觀表示此Level的功能,命令為TreeForm[expr][3]:

 Times[x, |              ]
      Sin[|         ]
         Plus[a, b]

Level的第二個參數的常見用法有:

  • Level[ex,n]:顯示1到n層的子表達式
  • Level[ex,{n}]:只顯示第n層的子表達式
  • Level[ex,{-n}]:顯示倒數第n層的子表達式

這種表示層次的方法在Mathematica中是通用的,很多的內置函數都採用此方法指明層次。具體用法請詳見幫助。

模式匹配與規則替換

[編輯]

Mathematica中還有一個非常基本的概念,這就是模式匹配(pattern-matching),模式匹配是一個將規則表達式對應起來的系統。一切功能都建立在模式匹配上,沒有了它,Mathematica不知道對表達式進行何種操作。模式匹配更側重於匹配表達式的結構而不是語義,也就是說它更關注的是表達式的結構是怎麼樣的而不是表達式能幹什麼。

規則重寫

[編輯]

一個典型的規則(rule),其中a和b都是某Mathematica表達式:

  • a->b

這條規則的意思是:每當碰到<a>的時候就把它替換為<b>。比如:

 In[1]:=	{a, c, d,c} /. a->b (*符号<expr/.rule>的意思是对表达式expr应用规则rule*)
Out[1]=	{b, c, d, c}

一個模式是某些部分被替換為「空白」("_",完整形式為Blank[])的表達式,簡單來說,這裡的所謂的「空白」就是一個能代表任意表達式的占位符。比方說,f[x_]就表示f[anything]。

一個用模式定義的簡單函數

[編輯]

In[2]:= Clear[f];
        f[x_] := x^2;
        {f[2], f["word"], f[Newton]}
Out[2]= {4, "word"2, Newton2}

函數的本質是規則:DownValues命令

[編輯]

可以用內置的DownValues命令來查看規則的內部形式。我們在剛才定義的<f>身上試試看:

In[3]:= DownValues[f] 
Out[3]= {HoldPattern[f[x_]] :>x2}

這裡出現的HoldPattern我們稍後討論。形如x_的模式是最簡單的模式,同時也有更多複雜的模式。有的模式還附帶了條件(condition),只有在滿足條件的情況下才匹配。欲知詳情,請往後看。

基於限制模式的函數

[編輯]

現在舉個例子:我們對前面定義的f加以限制,讓它只能作用於整數。

In[]:= Clear[f];
       f[x_Integer] := x^2;
       {f[2], f[Pi], f["word"], f[Newton]}
Out[]= {4, f[π], f[word], f[Newton]}

在這個例子裡,我們引入了一個更複雜的模式:x_Integer。

淺議計算

[編輯]

通過上面的例子可以看到,對於一個表達式,如果沒有一條規則的模式(箭頭左邊的項)與其匹配,那麼Mathematica將會把它原樣返回。這觸及到Mathematica的核心計算方法:

    • 接受一個輸入內核的表達式
    • 規則庫(存放所有的系統規則和用戶自定義規則的地方)里的所有規則都被應用到這個表達式
    • 如果發現某個規則的模式與這個表達式匹配,立即運用此規則並將表達式重寫並返回結果
    • 循環地重複這個過程

有時,沒有一條規則能匹配這個表達式。這個表達式就保持原有狀態並返回。因為規則庫里既有系統規則也有用戶自定義規則[4],這給表達式的操縱帶來極大的靈活性。

一個擁有多重定義的函數

[編輯]

現在定義一個新函數,這個函數對偶數進行線性計算,對奇數進行平方計算,對既不是奇數又不是偶數的數應用正弦函數(Sin):

In[]:= Clear[f];
       f[x_?EvenQ] :=x;
       f[x_?OddQ] :=x^2;
       f[x_] :=Sin[x];

試試給它不同類型的參數:

In[]:= {f[1], f[2], f[3], f[4], f[3/2], f[Newton], f[Pi]}
Out[]= {1, 2, 9, 4, Sin[3/2], Sin[Newton], 0}

順便說下,OddQ和EvenQ[5]是用來判斷奇偶性的內置函數,用法從字面應該很容易看出來。

替換規則的順序很重要

[編輯]

我們來看看上面<f>的第三個定義,f[x_] :=Sin[x] 。即無論遇到什麼輸入都計算其正弦。有的同學可能天真地據此認為上面代碼的結果會是一堆Sin,不幸的是,因為替換規則的應用是有序(一個接着一個)的,我們沒有得到一堆Sin。

首先,如果某次嘗試中一個規則正好與表達式相匹配,那麼計算就會馬上進行,剩下的規則也就不會再嘗試了;其次,如果有多條規則與一個表達式相匹配,應用在表達式上的第一條規則早已重寫的該表達式從而使其他的幾條規則不再匹配。

所以,最後的結果取決於嘗試規則的順序——Mathematica把規則庫中的規則一個接着一個地應用在表達式上,遇到一個「合適」的便不再嘗試。這就好比一個女神在一大堆男性仰慕者中找對象,她按照先來後到的順序把這群男士排成對,然後一個接着一個的相親,一旦遇上哪個高富帥符合她的標準就立馬收手,當天成親!剩下的不管是高富帥還是窮屌絲都直接轟走。

回到我們的<f>中來,f[x_] := Sin[x]是在最後定義的,所以對於奇數或偶數的參數來說,Sin[x]是根本沒有機會的!

規則的自動重排

[編輯]

看起來定義規則的順序對最後的結果十分重要,那我們已不是要處處小心定義的順序嗎?其實,Mathematica足夠聰明,對於一些簡單的情況它是能自動區別優先級的。比方說對於上面的那個例子,就算我們把f[x_]:=Sin[x]放在第一個定義,結果還是不會變。因為Mathematica能夠自動重排規則。

In[]:= Clear[f];
       f[x_]:=Sin[x];
       f[x_?EvenQ]:=x;
       f[x_?OddQ]:=x^2;
       {f[1], f[2], f[3], f[4], f[3/2], f[Newton]}
Out[]= {1,2,9,4,Sin[3/2],Sin[Newton]}

用DownValues瞧瞧定義在<f>上的規則是怎麼排列的:


 In[]:= DownValues[f]
 Out[]= {HoldPattern[f[x_?EvenQ]] :> x, HoldPattern[f[x_?OddQ]] :> x^2, HoldPattern[f[x_]] :> Sin[x]}

喏,Mathematica很聰明吧!Sin那條規則又被排到了最後,雖然它是最先定義的。Mathematica內置了一個「規則分析器」,這東西會儘量(是的,它盡力了)在把普遍規則挪到特殊規則後面。當然了,這一招不會總是奏效,有時也會不盡人意,應當小心一點。事實上,需要你手動更改規則順序的情況是極其少見的。

表達式的計算

[編輯]

上個例子引入了第三個原則:表達式的計算和規則重寫。其實講的就是一個Mathematica計算到底是怎麼完成的。這個前面也提到過一點,這裡用通俗的語言進行描述:當你在筆記本中輸入任意一個表達式並按下shift+enter,這個表達式就被送入內核中計算。接下來Mathematica就在全局規則庫中找能與這個表達式相匹配的替換規則(形如object1->object2)。如果找到了,這個表達式或者它的子表達式就被重寫,然後這個過程從頭開始。這個過程會一直循環下去直到全局規則庫中找不出與之匹配的規則。這時的表達式(被重寫n次)就被當作最後的結果輸出了。[6]

全局規則庫里包含了系統內置的規則和用戶自定義規則。通常用戶自定義的規則優先級要高一點[7]。實際上,所有的變量賦值、函數定義都是通過存儲在全局規則庫中的某種規則實現的。也就是說,函數和變量之間沒有本質的區別,They are just rules!

舉個例子:

 
  In[]:= FullForm[Sin[Pi+Pi]]
  Out[]= 0

什麼?您期待的結果是Sin[Plus[Pi,Pi]]?這是因為規則庫中有類似於Plus[x_,x_]->2 x和Sin[2 Pi]->0的規則,又因為默認的計算順序是從表達式的內層開始的,所以等到FullForm大顯身手的時候Sin[Pi+Pi]已經被重寫為0了。用內置函數Trace可以監視表達式的技術過程:

 
  In[]:= Trace[FullForm[Sin[Pi+Pi]]]
  Out[]= {{{π+π,2 π},Sin[2 π], 0}, 0}

總結

[編輯]

我們簡要的說明了Mathematica的核心理念,同時也介紹了一些內置函數:AtomQ、Head、FullForm、TreeForm、Level、Plus、Times、Trace、DownValues、OddQ、EvenQ。

註記

[編輯]
  1. 查看一個表達式expr頭部的方法為Head[expr]
  2. 內置函數TreeForm,可以用來顯示表達式的樹形結構
  3. 為了方便起見,這裡顯示的是文本界面下的TreeForm,在筆記本界面下使用該命令可以得到更加形象的結果
  4. 自定義規則的優先級比系統規則高
  5. 以Q結尾的內置函數一般用來進行某種判斷,並返回真值True或False,前面已經遇到的有AtomQ。
  6. 這段說明是極簡版本,雖然主要的思想是這樣的,但是實際的過程卻要微妙和複雜得多。
  7. 這讓重新定義內置函數成為可能