(拙いところがあって、今見ると直したいのだけど… あくまでも C++ で複素数を扱うにはどうするかのサンプルと考えれば、 まあいいかと。)
代数方程式を連立法 (Durand-Kerner法ともいう) で解くプログラムである。
「非線型方程式、特に代数方程式の解法」 の付録B「応用解析IVの数値実験」に解説がある。 実行結果は https://m-katsurada.sakura.ne.jp/labo/text/nonlinear-algebraic/node85.htmlに載っている。
GLSC と X11 を使っているので、コンパイルするときに、 インクルード・ファイルやライブラリィの置き場所を指定する必要がある (簡単には実行して試せないです。あしからず。)。 最近の私の環境 (Mac で XQuartz というのを使っていて、 GLSC を /usr/local/include と /usr/local/lib に置いている) では
g++ -I/usr/local/include -I/usr/X11/include DK3.C -L/usr/local/lib -L/usr/X11/lib -lglscd -lX11 |
// DK3.C -- DKA 法で代数方程式の根を求める
//
// 数学科 6701 号室のワークステーション環境でのコンパイル
// (グラフィックス・ライブラリィ GLSC を使っているので
// 情報科学センターではコンパイルできない。悪しからず。)
// g++ -o DK3 DK3.C -I/usr/local/include -lglscd -lX11 -lsocket
// あるいは
// ccmg+ DK3.C
//
// Win32 環境下の glscwin でも (若干の修正の下に) 動作させられることが
// 分かったので、画面に表示するメッセージを変更しました (2000/10/18)。
//
// 2016/3/22 修正
// (1) #include するのは .h なし
// (2) using namespace std;
// (3) #include <cstdlib> // exit() のため
// (4) int 型の n を (double)n とキャスト
// (5) "DKGRAPH" を (char *)"DKGRAPH" (string は char * と違うのだそうな)
// g++ -I /usr/X11/include DK3.C -L/usr/local/lib -lglscd -L/usr/X11/lib -lX11
//
// 2016/12/30 修正
// extern { ... }; 最後の ; を忘れていたのを修正。
#include <iostream>
#include <iomanip> // setprecision() のため
//#include <math>
#include <complex> // complex<double> のため
#include <cstdlib> // std::exit() のため
using namespace std;
// GLSC のヘッダーファイルをインクルード
extern "C" {
#define G_DOUBLE
#include <glsc.h>
};
// プロトタイプ宣言
complex<double> polynomial(int, complex<double> *, complex<double>);
complex<double> bunbo(int, complex<double> *, int);
// 解きたい代数方程式の次数の最高値
#define MAXN 100
int main(void)
{
int i, k, n;
complex<double> a[MAXN+1], x[MAXN], newx[MAXN], dx[MAXN];
complex<double> g, I(0,1);
double r0, R, max, pi;
// 数学定数 (円周率) の準備
pi = 4 * atan(1.0);
// 表\示の桁数を 16 桁にする
cout << setprecision(16);
// 方程式の入力
cout << "次数 n を入力してください (1≦n≦" << MAXN << "): ";
cin >> n;
if (n > MAXN || n <= 0) {
cerr << "次数は" << MAXN << "以下の自然数として下さい。" << endl;
exit(0);
}
for (i = 0; i <= n; i++) {
cout << (n-i) << "次の係数を入力してください: ";
cin >> a[i];
}
// 多項式を最高次の係数で割って monic にする
cout << "monic にします。" << endl;
for (i = 1; i <= n; i++)
a[i] /= a[0];
a[0] = 1;
cout << "修正した係数" << endl;
for (i = 0; i <= n; i++)
cout << "a[" << i << "]=" << a[i] << endl;
// Aberth の初期値を配置する円の決定
g = - a[1] / (double)n;
cout << "根の重心" << g << endl;
max = 0;
for (i = 1; i <= n; i++)
if (abs(a[i]) > max)
max = abs(a[i]);
cout << "max|a_i|=" << max << endl;
r0 = abs(g) + 1 + max;
cout << "根は重心中心で、半径 r0=" << r0 << "の円盤内にある" << endl;
cout << "円の半径 (分からなければ上の値を指定してください): ";
R = r0;
cin >> r0;
if (r0 > R)
R = r0;
cout << "図は根の重心を中心として半径 " << R
#ifdef __CYGWIN__
<< "の円が表\示できるようにします。" << endl;
#else
<< "の円が表示できるようにします。" << endl;
#endif
// Aberth の初期値
cout << "初期値" << endl;
for (i = 0; i < n; i++) {
double theta;
theta = 2 * i * pi / n + pi / (2 * n);
x[i] = g + r0 * exp(I * theta);
cout << x[i] << endl;
}
// グラフィックス・ライブラリィ GLSC の初期化
g_init((char *)"DKGRAPH", 120.0, 120.0);
g_device(G_BOTH);
// 座標系の指定
g_def_scale(0,
real(g) - 1.1 * R, real(g) + 1.1 * R,
imag(g) - 1.1 * R, imag(g) + 1.1 * R,
10.0, 10.0, 100.0, 100.0);
g_sel_scale(0);
// 線種、マーカーの指定
g_def_line(0, G_BLACK, 0, G_LINE_SOLID);
g_sel_line(0);
g_def_marker(0, G_RED, 2, G_MARKER_CIRC);
g_sel_marker(0);
// 初期値と初期値を置く円を描く
g_circle(real(g), imag(g), r0, G_YES, G_NO);
for (i = 0; i < n; i++)
g_marker(real(x[i]), imag(x[i]));
// 反復
for (k = 1; k <= 1000; k++) {
double error;
cout << "第" << k << "反復" << endl;
for (i = 0; i < n; i++) {
dx[i] = polynomial(n, a, x[i]) / bunbo(n, x, i);
newx[i] = x[i] - dx[i];
// 図示する
g_move(real(x[i]), imag(x[i]));
g_plot(real(newx[i]), imag(newx[i]));
g_marker(real(newx[i]), imag(newx[i]));
}
// 更新
for (i = 0; i < n; i++) {
x[i] = newx[i];
cout << x[i] << endl;
}
// 変化量を計算する (ここは非常に素朴)
error = 0.0;
for (i = 0; i < n; i++)
error += abs(dx[i]);
cout << "変化量=" << error << endl;
// 変化量が小さければ収束したと判断して反復を終了する。
if (error < 1e-12)
break;
}
// GLSC 終了の処理
cout << "終了には" << endl;
cout << " グラフィックスのウィンドウをクリック (X の場合)。" << endl;
cout << " グラフィックスのウィンドウをクローズ (Windows の場合)。" << endl;
g_sleep(-1.0);
g_term();
return 0;
}
// 多項式の値の計算 (Horner 法)
complex<double> polynomial(int n,
complex<double> *a,
complex<double> z)
{
int i;
complex<double> w;
w = a[0];
for (i = 1; i <= n; i++)
w = (w * z + a[i]);
return w;
}
// Π (zi-zj) の計算
// j≠i
complex<double> bunbo(int n,
complex<double> *z,
int i)
{
int j;
complex<double> w(1,0);
for (j = 0; j < n; j++)
if (j != i)
w *= (z[i] - z[j]);
return w;
}