プログラミング演習1
― 課題4 数値解析 ―
2.2 ニュートン法
ニュートン法は以下の手順で方程式 f (x)=0 の解を求める。
- 初期値x0を与える。
- その点での関数値f (x0)と 傾きf '(x0)から x 切片x1を推定する。
- x1を新たな初期値x0としてこの操作を反復する。
![]()
(x0, f (x0))を通る接線の式
y - f (x0) = f '(x0) (x - x0)
においてx 切片では y = 0, x = x1 となるので
x1 = x0 - f (x0) / f '(x0)
f (x) = x2 - 2 のとき、f '(x) = 2x だから
x1 = x0 - (x02 - 2) / 2x0 = (x0 + 2 / x0) / 2
真野芳久「Pascalプログラミングの基礎」サイエンス社 p.41 のプログラムと 川上一郎「数値計算」岩波書店 p.180 付録3.1のプログラムを合体してCに移植し、 手直ししたもの(数値解析の講義用Webページで公開している Newton.c を一部手直し) を以下に示す。
# ただし、練習のためにバグが入れてある。
下線部では scanf関数の代わりにgets関数とsscanf関数 を使って入力する方法を用いた。
また、リダイレクト出力時にも入力促進メッセージ等がコマンドプロンプトに 出力されるようにfprintf関数を用いて 標準エラー出力(stderr)とした。1: /* 2: * Newton.c: ニュートン法 3: */ 4: #include <stdio.h> // printf, gets, sscanf 5: #include <math.h> // fabs 6: 7: main() 8: { 9: int n = 0; 10: double a, x, x0, err, eps = 1.0 e-10; 11: char s[128]; 12: 13: fprintf(stderr, " a = "); gets(s); sscanf(s, "%lf", &a); 14: while (a <= 0.0) { 15: fprinrf(stderr, "'a' には正の数を入れてください。\n"); 16: fprintf(stderr, " a = "); gets(s); sscanf(s, "%1f", &a) 17: } 18: 19: x = (a + 1.0) / 2.0; 20: printf("# n, x, err\n"); 21: printf("%4d, % .15e\n", n, x); 22: do { 23: n ++; 24: x0 = x; 25: x = (x0 + a/x0) / 2; 26: err = fabs(x - x0); 27: printf("%4d, % .l5e, % .l5e\n", n, x, err) 28: } while (err >= eps); 29: printf("\n# sqrt(%e) = %.15e\n",a, x); 30: retrun (O); 31: }主要部分のアルゴリズムをPADで描くと
" a = 2" を入力したときの実行結果とそのグラフは
![]()
2.3 二元連立非線形方程式(ニュートン法)
n 元連立非線形方程式は一般に
f1(x1, x2, ..., xn) = 0,
f2(x1, x2, ..., xn) = 0,
... ... ... ... ...
fn(x1, x2, ..., xn) = 0
と書くことができる。これをベクトル表記すると
f (x) = 0
となる。このとき、ニュートン法の計算式は
2変数の場合は、逆行列が簡単に求められるので
xk+1 = xk + u (xk)
= xk - J (xk)-1 f (xk)
となる。ここに、J (xk) はヤコビ行列と呼ばれ、
であり、また、 u (x) = - J (x)-1 f (x) である。
ここに
![]()
「数値解析」の講義用Webページで公開している 「連立非線形方程式」を解くプログラム(Newton2.c)を 一部手直ししたものを以下に示す。 このプログラムは
x 2 + y 2 = 1, y = x 3
を解くためのものである。仕様は以下のとおり。
- 入力は初期値(x0, y0)のみ。許容誤差は eps = 10-10。
- 出力は途中経過と最終結果。CSV形式に対応。
- 方程式の左辺の関数2つはおのおの独立に定義。(結果は戻り値で返す。)
- ヤコビ行列の4つの要素もおのおの独立に定義。(結果は戻り値で返す。)
- 補正ベクトルu (x)計算ルーチンの結果は2つの引数で返す。
- 2変数ニュートン法ルーチンの計算結果は2つの引数で返す。
1: /* 2: * Newton2.c: 2変数のニュートン法 3: * x2+y2=1, y=x3 -> (x,y)=(0.826,0.5636),(-0.826,-0.5636) 4: */ 5: #include <stdio.h> // printf, fprintf, gets, sscanf 6: #include <math.h> // hypot(hypotenuse): 直角三角形の斜辺を求める関数 7: 8: double f1(double x, double y) 9: { 10: return x * x + y * y - 1; // f1(x,y) = x2 + y2 - 1 11: } 12: double f2(double x, double y) 13: { 14: return x * x * x - y; // f2(x,y) = x3 - y 15: } 16: 17: double J11(double x, double y) 18: { 19: return 2 * x; // df1(x,y)/dx = 2x 20: } 21: double J12(double x, double y) 22: { 23: return 2 * y; // df1(x,y)/dy = 2y 24: } 25: double J21(double x, double y) 26: { 27: return 3 * x * x; // df2(x,y)/dx = 3x2 28: } 29: double J22(double x, double y) 30: { 31: return -1; // df2(x,y)/dy = -1 32: } 33: 34: int ucalc(double x, double y, double *ux, double *uy) 35: /* 36: * u = -J-1(x)*f(x) 37: */ 38: { 39: double det; 40: 41: det = J11(x, y) * J22(x, y) - J12(x, y) * J21(x, y); 42: *ux = -1 / det * (J22(x, y) * f1(x, y) - J12(x, y) * f2(x, y)); 43: *uy = 1 / det * (J21(x, y) * f1(x, y) - J11(x, y) * f2(x, y)); 44: return 0; 45: } 46: 47: int Newton2(double *x, double *y, double eps) 48: { 49: int n=0; 50: double x0, y0, ux, uy, err; 51: 52: printf("# n, x, y, err\n"); 53: printf("%4d, % .15e, % .15e\n", n, *x, *y); 54: do { 55: n++; 56: x0 = *x; y0 = *y; 57: ucalc(*x, *y, &ux, &uy); 58: *x += ux; *y += uy; // xk+1 = xk + u(xk) 59: err = hypot(*x - x0, *y - y0); 60: printf("%4d, % .15e, % .15e, % .15e\n", n, *x, *y, err); 61: } while (err >= eps); 62: return 0; 63: } 64: // ------------ Main ------------ 65: main() 66: { 67: double x=1, y=0.5, eps = 1e-10; 68: char s[128]; 69: 70: fprintf(stderr, "# x0 y0 = "); gets(s); sscanf(s, "%lf%lf", &x, &y); 71: Newton2(&x, &y, eps); 72: printf("\n# (x y) = (% .15e % .15e)\n", x, y); 74: return 0; 75: }"# x0 y0 = 1 0.5" と入力したときの実行結果とグラフは
![]()
課題4-2
上記2つのプログラムを以下の要求に沿ってできるところまで改良せよ。 満たせなかったものがあるときは、それをレポートに明示すること。 以下の(2)の2変数のニュートン法の課題をある程度できた人は、(1)については省略してよい。(1) 2.2 のニュートン法のプログラム Newton.c は、 2.1 の二分法や 2.3 の連立非線形方程式のプログラムのように 方程式の左辺の関数が独立していない。 そこで、以下の要求を満たすプログラムを作成せよ。
(1-1) 方程式の左辺関数 f (x) (= x 2 - a ) および その導関数 df (x)/dx を独立させる。 ただし、入力変数 a はグローバル変数としてよい。
(1-2) ニュートン法のアルゴリズムの部分 (20行目〜28行目)を Newton() と名づけ、main関数から独立させる。
double Newton(double x, double eps) { ………………………………… ニュートン法のアルゴリズム ………………………………… return x; }(1-3) 方程式の左辺の関数 f (x) と その導関数の名前をmain関数側で自由に指定できるように、 Newton() の引数に関数ポインタを含めて定義する。
double Newton(double x, double (*f)(double), double (*df)(double), double eps) { ………………………………… ニュートン法のアルゴリズム ………………………………… return x; }なお、実行例として a = 3 の場合について報告せよ。(2) 2.3 の2変数のニュートン法のプログラム Newton2.c は、 同じような関数が多くてソースが冗長である。 そこで、以下の要求を満たすプログラムを作成せよ。
(2-1) 関数f1(), f2()を配列 f[1], f[2]、 関数J11()〜J22()を2次元配列 J[1][1]〜J[2][2] に割り当てることにして、 方程式の左辺関数f (x)と そのヤコビ行列 J (x)を おのおの一つのサブルーチン(func(), Jcalc())にまとめること。
ただし、関数の戻り値で渡していた値は引数で受け渡すことにし、 手続きの戻り値は終了コード(整数)とする。int func(double x, double y, double f[]) { …………………… f[1] = …………; f[2] = …………; return 0; }また、Cでの配列の定義は0から始まることに注意し、一つ多めに宣言しておく。 配列の数(ND)をあらかじめ以下のように定数マクロで宣言しておくとよい。
#define ND 32次元配列を引数でうまく渡せなかった人は、 次善の策として1次元配列にして渡すか、グローバル変数で引き渡すこと。
(2-2) 変数x, yを配列x[1], x[2]、 変数ux, uyをu[1], u[2]に割り当ててサブルーチン等の引数の数を減らし、 n 変数に移行しやすくする。
(2-3) 方程式の左辺の関数 f (x) と その導関数の名前をmain関数側で自由に指定できるように、 Newton2() の引数に関数ポインタを含めて定義する。
なお、実行例では
x 2 + (y/2)2 = 1, y 2 = x 2 + 1
について、第1象限の解を求めたときの結果と途中経過のグラフを報告せよ。
【目次】 | 【1.】 | 【2.のトップ】 | 【3.】 | 【付録1】 【付録2】