はじめての Maxima プログラミング

この Notebook は,

のプログラミング部分に相当する内容を JavaScript のかわりに Maxima を使って書いてみたものです。

章番号はオリジナルの従来テキストに対応するように8章からはじめていますので,従来テキストを使用して授業を展開してきた教員や受講学生にも対応がわかりやすいかと思います。

また,テキストや授業と関係なく,初めて Jupyter Notebook で Maxima を利用する学生や教員にとっても参考になるかも知れません。

Maxima はコンピュータ代数システム(いわゆる数式処理システム)ですが,構造化プログラミングの機能もあり,条件分岐や繰り返し処理などを利用することで,プログラミング言語としても利用できます。この Notebook では,プログラミング言語としての共通部分に焦点を絞って解説します。コンピュータ代数システム Maxima 固有の機能の活用例については,最終節のリンクを参照してください。

では,はじめましょう。

はじめてのプログラミング

エンゲル係数とは,家計の消費支出に占める飲食費(食料費,食費)の割合(パーセント単位)です。

消費支出 100,000円,飲食費 40,000円の人のエンゲル係数を求める例。Maxima では以下のような電卓的な使い方ですぐ答えが出ます。

本稿のように Jupyter Notebook 環境では,以下の1行を書いたあとに,Shift キーを押しながら Enter キー(または Return キー)を押して実行します。

Maxima の文末は,;$ で終わります。$ で終わる場合は結果が表示されません。

In [1]:
40000/100000 * 100;
Out[1]:
\[\tag{${\it \%o}_{1}$}40\]

ここからは,単に電卓的に使うだけではなく,応用・発展も見据えて以下のようなプログラム文を作成して実行することにします。

以下の例では,

  1. 1行目で変数 s1 に消費支出である 100000(円)という数値を代入し,
  2. 2行目で変数 s2 に飲食費である 40000(円)という数値を代入し,
  3. 3行目でエンゲル係数を計算して,変数 x1 に代入し,表示させています。

Maxima では,変数への代入は : です。

In [2]:
s1: 100000$
s2: 40000$
x1: s2*100/s1;
Out[2]:
\[\tag{${\it \%o}_{4}$}40\]

エラーとデバグ

意に反して,プログラムがうまく動作しない場合があります。たとえば,上記の 3行目を以下のように書いた場合。

掛け算を示す演算子は *,割り算を示す演算子は / であるところを誤って,x\ と書くと,これらの記号は Maxima では演算子として定義されていないのでエラーとなります。このようなエラーは文法エラー(incorrect syntax) と呼ばれ,Jupyter Notebook では,以下のように誤りやその箇所を指摘してくれます。

In [3]:
s1: 100000$
s2: 40000$
x1: s2 x 100 \ s1;
incorrect syntax: x is not an infix operator
x1: s2 x 
       ^

   [Condition of type MAXIMA-JUPYTER::MAXIMA-SYNTAX-ERROR]

#<ENVIRONMENT {10032D4A23}>
   [Environment of thread #<THREAD "SHELL Thread" RUNNING {1006380003}>]


Backtrace:
 5: (SB-KERNEL::%SIGNAL incorrect syntax: x is not an infix operator
x1: s2 x 
       ^
)
 6: (ERROR incorrect syntax: x is not an infix operator
x1: s2 x 
       ^
)
 7: ((LAMBDA (ORIG &REST ARGS) :IN "/usr/local/share/maxima-jupyter/local-projects/maxima-jupyter/src/overrides.lisp") #<FUNCTION MREAD-SYNERR {520527BB}> ~A is not an infix operator X)
 8: ((LAMBDA (&REST MAXIMA-JUPYTER::ARGS)) ~A is not an infix operator X)
 9: (LED-CALL $X ($ANY . $S2))
 10: (PARSE $ANY 20)
 11: (PARSE-INFIX $: ($ANY . $X1))
 12: (LED-CALL $: ($ANY . $X1))
 13: (PARSE $ANY 0)
 14: (MREAD-RAW #<STRING-INPUT-STREAM {1002F07613}> NIL)
 15: (DBM-READ #<STRING-INPUT-STREAM {1002F07613}> NIL NIL NIL)
 16: (MAXIMA-JUPYTER::MY-MREAD #<STRING-INPUT-STREAM {1002F07613}>)
 17: (MAXIMA-JUPYTER::READ-AND-EVAL #<KERNEL {1003FA0F23}> #<STRING-INPUT-STREAM {1002F07613}> T)
 18: ((:METHOD JUPYTER:EVALUATE-CODE (MAXIMA-JUPYTER::KERNEL T)) #<KERNEL {1003FA0F23}> s1: 100000$
s2: 40000$
x1: s2 x 100 \ s1;)
 19: (JUPYTER::HANDLE-EXECUTE-REQUEST #<KERNEL {1003FA0F23}> #<MESSAGE {1002ED07D3}>)
 20: (JUPYTER::HANDLE-SHELL-MESSAGE #<KERNEL {1003FA0F23}> #<MESSAGE {1002ED07D3}>)
 21: (JUPYTER::RUN-SHELL #<KERNEL {1003FA0F23}>)
 22: ((LAMBDA NIL :IN BORDEAUX-THREADS::BINDING-DEFAULT-SPECIALS))
 23: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
 24: ((FLET "WITHOUT-INTERRUPTS-BODY-11" :IN SB-THREAD::RUN))
 25: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
 26: ((FLET "WITHOUT-INTERRUPTS-BODY-4" :IN SB-THREAD::RUN))
 27: (SB-THREAD::RUN)
 28: ("foreign function: call_into_lisp")
 29: ("foreign function: funcall1")

別の種類の誤りもあります。たとえば,「100」を「1000」と入力した場合...

In [4]:
s1: 100000$
s2: 40000$
x1: s2*1000/s1;
Out[4]:
\[\tag{${\it \%o}_{9}$}400\]

上記の例では incorrect syntax (文法エラー)の警告が出ませんが,答えは明らかに変です。(エンゲル係数の最大値は100だから。)

このような場合はコンピュータにとって誤りではなく,指示通りに計算し,その結果を表示します。 このようなプログラム作成者の不注意や勘違いによって生じるエラーには注意が必要です。

プログラムが正しく動作しない場合は,誤りの箇所を探し,修正して再度動作を確認します。正しく動作するようになるまで,プログラムを修正し,実行(Shift + Enter)します。この作業をデバグといいます。

データの型と変数

上記の例の 1行目では,s1 という名前の変数に 100000 という 数値を代入しています。変数名はアルファベットで始まる英数字列にします。大文字と小文字は区別されます。

変数 ss に文字列を代入する場合は,以下のように " で囲みます。

In [5]:
ss: "エンゲル係数"$
print(ss)$
エンゲル係数

式と演算

掛け算を示す演算子は *,割り算を示す演算子は /。足し算は +,引き算は - です。

文字列の連結演算は concat() を使います。以下の例を参照。

In [6]:
x0 : "エンゲル"$
y0 : "係数"$
z0 : concat(x0, y0);
Out[6]:
\[\tag{${\it \%o}_{14}$}\mbox{ エンゲル係数 }\]

文字列と数値を直接連結するときも,concat() を使います。

In [7]:
s1 : 100000 $
s2 : 40000 $
x1 : s2*100/s1 $
z1 : concat("エンゲル係数は ", x1, " です。");
Out[7]:
\[\tag{${\it \%o}_{18}$}\mbox{ エンゲル係数は 40 です。 }\]

文字列と連結しなくても,以下のようにも書くことができます。 print() 文は,カンマ (,) で区切ることによって複数の項目(変数)を表示することができます。

In [8]:
print("エンゲル係数は ", x1,  " です。")$
エンゲル係数は \(40\) です。

主な演算子

加減乗除の演算子 +, -, *, /

優先的に計算したい箇所は丸括弧 ( )で グループ化します。

In [9]:
2 + (50 - 5 * 6)/4;
Out[9]:
\[\tag{${\it \%o}_{20}$}7\]

Maxima の割り算は以下のように厳密な値を返します。

In [10]:
17 / 3;
Out[10]:
\[\tag{${\it \%o}_{21}$}\frac{17}{3}\]

べき乗は ** または ^ です。

In [11]:
2**4;
2^4;
Out[11]:
\[\tag{${\it \%o}_{22}$}16\]
Out[11]:
\[\tag{${\it \%o}_{23}$}16\]

対話型プログラム

上記の例では,プログラムの中で消費支出や飲食費を決めていましたが,今度はプログラムを実行する人が入力できるようにしてみます。

read() 関数は入力された値を返します。注意: read() に対して値をキーボードから入力する場合も,最後は ; をつけてから Enter キーを押します。

In [12]:
s1 : read("消費支出? ")$
s2 : read("飲食費? ")$
x1 : float(s2*100/s1)$ /* float() で実数に変換します */
print("エンゲル係数は ", x1, " です。")$
消費支出?
100000;
飲食費?
40000;
エンゲル係数は \(40.0\) です。

関数の定義と呼び出し

(これまでの例では1回しか計算していませんが)よく使う計算を「関数」として定義する例です。

以下の例では,1行目でエンゲル係数を計算する関数 calc() を定義し,5行目でその関数を呼び出しています。

In [13]:
calc(s1, s2):= float(s2*100/s1)$

s1 : read("消費支出? ")$
s2 : read("飲食費? ")$
x1 : calc(s1, s2)$
print("エンゲル係数は ", x1, " です。")$
消費支出?
100000;
飲食費?
40000;
エンゲル係数は \(40.0\) です。

いったん定義された関数は,以下のように何回でも利用できます。

In [14]:
calc(120000, 53000);
Out[14]:
\[\tag{${\it \%o}_{33}$}44.16666666666666\]

(練習)

消費支出を入力すると,エンゲル係数が 20 ~ 40 となる飲食費を表示するプログラムをつくりなさい。

数学関数を使う

エンゲル係数を計算する部分はすでに関数 calc() として定義していますが,プログラム全体を関数 engel() として定義してしまいます。

以下の例のように,block() は複数の関数や式を ,(カンマ)で区切って記述します。

In [15]:
engel():= block(
  s1 : read("消費支出? "),
  s2 : read("飲食費? "),
  x1 : calc(s1, s2),
  print("エンゲル係数は ", floor(x1), " です。")
)$
In [16]:
engel()$
消費支出?
100000;
飲食費?
40000;
エンゲル係数は \(40\) です。

上記の例では,x 以下の最大の整数を与える関数 floor() を使って,小数点以下を切り捨てたエンゲル係数の値を表示させています。

Maxima で使える数学関数

Maxima で使える数学関数や数学定数については,以下のマニュアルのページを参照してください。

(練習)

小数点以下を切り上げたエンゲル係数の値を表示させるように上記のプログラムを変更しなさい。

ヒント: Maxima 5.42.2 Manual: 10. Mathematical Functionsceiling() を参照。

参考:数値の丸めと四捨五入

Maxima の組み込み関数に round() があります。数値を丸める関数ですが,いわゆる四捨五入とはちょっと違うようです。

以下の例からわかるように,小数点以下が 0.5 未満の場合と 0.5 を超える場合は四捨五入と同様の丸め方ですが,ちょうど 0.5 のときは,偶数に丸められていることがわかります。

これを偶数への丸めといい,端数が 0.5 より小さいなら切り捨て,端数が0.5 より大きいなら切り上げ,端数がちょうど 0.5 なら切り捨てと切り上げのうち結果が偶数となる方へ丸めます。

In [17]:
print(round(12.49), "  ", round(12.50), "  ", round(12.51))$
\(12\) \(12\) \(13\)
In [18]:
print(round(13.49), "  ", round(13.50), "  ", round(13.51))$
\(13\) \(14\) \(14\)

(練習)

四捨五入した値を表示する関数をつくりなさい。

条件分岐

条件分岐とは,例えばエンゲル係数の値によって実行文を変えることです。例を示します。

関数 engel() を以下のように変更します。

In [19]:
engel():= block(
  s1 : read("消費支出? "),
  s2 : read("飲食費? "),
  x1 : calc(s1, s2),
  if x1 >= 80 then 
      print("エンゲル係数は ", round(x1), " です。飲食費を使いすぎです。")
  else
      print("エンゲル係数は ", round(x1), " です。")
)$
In [20]:
engel()$
消費支出?
100000;
飲食費?
85000;
エンゲル係数は \(85\) です。飲食費を使いすぎです。
In [21]:
engel()$
消費支出?
120000;
飲食費?
45000;
エンゲル係数は \(38\) です。

if 文の書式は以下の通りです。

if 条件式1 then
    条件式1が満たされる場合に実行する文
  elseif 条件式2 then
    条件式2が満たされる場合に実行する文
  else
    上記以外の場合に実行する文 $

(練習)

上記の条件分岐を,エンゲル係数が

  1. 80 以上の場合には「飲食費を使いすぎです。」
  2. 80 未満 40 以上の場合には「正常です。」
  3. 40 未満の場合には「もっと食べましょう。」

と表示させるように変更しなさい。

繰り返し処理

以下の例では,1行目で代入された消費支出に対してエンゲル係数を計算します。

1回だけ計算して表示するのではなく,2行目の飲食費の初期値(ここでは 40000 円)から,5行目の条件式(ここでは 80000 円以下)が成り立つ場合に,8行目にあるように 5000 円ずつ増やしながらエンゲル係数を表示します。

In [22]:
s1 : 100000 $
s2 : 40000 $
print("消費支出", s1, "円に対するエンゲル係数の値")$

while s2 <= 80000 do(
    x1 : calc(s1, s2),
    print("   飲食費", s2, "円の場合: ", x1),
    s2 : s2 + 5000
)$
消費支出 \(100000\) 円に対するエンゲル係数の値
飲食費 \(40000\) 円の場合: \(40.0\)
飲食費 \(45000\) 円の場合: \(45.0\)
飲食費 \(50000\) 円の場合: \(50.0\)
飲食費 \(55000\) 円の場合: \(55.0\)
飲食費 \(60000\) 円の場合: \(60.0\)
飲食費 \(65000\) 円の場合: \(65.0\)
飲食費 \(70000\) 円の場合: \(70.0\)
飲食費 \(75000\) 円の場合: \(75.0\)
飲食費 \(80000\) 円の場合: \(80.0\)

while 文の書式は以下の通りです。

while 条件式 do(
    条件式が成り立つ場合に実行する式1, 
    条件式が成り立つ場合に実行する式2, ... ,
    条件式が成り立つ場合に実行する式
)

同様の繰り返し処理を for 文を使って行うこともできます。

In [23]:
s1 : 100000 $
s2 : 40000 $
print("消費支出", s1, "円に対するエンゲル係数の値")$

for i: 40000 thru 80000 step 5000
  do(
    x1 : calc(s1, s2),
    print("   飲食費", s2, "円の場合: ", x1),
    s2 : s2 + 5000
  )$
消費支出 \(100000\) 円に対するエンゲル係数の値
飲食費 \(40000\) 円の場合: \(40.0\)
飲食費 \(45000\) 円の場合: \(45.0\)
飲食費 \(50000\) 円の場合: \(50.0\)
飲食費 \(55000\) 円の場合: \(55.0\)
飲食費 \(60000\) 円の場合: \(60.0\)
飲食費 \(65000\) 円の場合: \(65.0\)
飲食費 \(70000\) 円の場合: \(70.0\)
飲食費 \(75000\) 円の場合: \(75.0\)
飲食費 \(80000\) 円の場合: \(80.0\)

for 文の書式は以下の通りです。

for ivar: istart thru iend step istep
  do(
    ivar が istart から iend の間,実行される文
  )$

ivar は整数変数で,その初期値は istart。文が実行されるごとに,ivar の値は istep だけ加算され,iend の値になるとループが終了します。

step の部分を省略すると,1 ずつ加算されます。

(練習)

以下の値を求めるプログラムを作成せよ。

$$\sum_{n = 1}^{10} n^2$$

さらに Maxima を活用するために

この Notebook では,プログラミング言語として共通の部分に焦点を絞った Maxima の使い方について解説しました。

微分・積分や方程式の解法,さらにはグラフ作成といった,コンピュータ代数システム Maxima 固有の機能の活用例については,以下のページを参考にしてください。