DK(Durand-Kerner)法について

 投稿者:tibita  投稿日:2014年10月14日(火)17時32分32秒
  C言語のプログラミングについて勉強しています。
8次の代数方程式をDK法で解き、実部、虚部の解を共にモニタ上に出力するプログラムを作りたいのですが、C言語で書かれたサンプルプログラムを書いていただけませんか?
 

Re: DK(Durand-Kerner)法について

 投稿者:山中和義  投稿日:2014年10月16日(木)10時37分3秒
  > No.3525[元記事へ]

tibitaさんへのお返事です。

> C言語のプログラミングについて勉強しています。
> 8次の代数方程式をDK法で解き、実部、虚部の解を共にモニタ上に出力するプログラムを作りたいのですが、C言語で書かれたサンプルプログラムを書いていただけませんか?


代数方程式
 p(z)=z^8-36z^7+546z^6-4536z^5+22449z^4-67284z^3+118124z^2-109584z+40320
の解

最近の言語では、COMPLEX(複素数)を扱えるので、記述はより簡潔になると思います。

下記のANSI C程度のサンプルは、実部と虚部( x1(n), x2(n) )を分離して計算しています。



//DKA(Durand-Kerner-Aberth)法によるn次代数方程式f(x)=0の解法

#include <stdio.h>
#include <math.h>

#define PI 3.14159265358979323846
#define N 8 //n次

int main(void)
{
   double a[N+1] = {40320,-109584,118124,-67284,22449,-4536,546,-36,1}; //係数
   double x1[N],x2[N]; //n個の根(実部、虚部)
   int i,j;

   for (i=0; i<=N; i++) a[i]/=a[N]; //x^n+a[n-1]/a[n]*x^(n-1)+ … + a[1]/a[n]*x+a[0]/a[n]の形へ

   double r=1.0, r0, t; //初期値を仮定する
   for (i=1; i<=N; i++) {
      r0=pow(fabs(N*a[i]),1.0/i);
      if (r<r0) r=r0;
   }
   for (i=1; i<=N; i++) { //半径rの円に等間隔に配置する
      t=2.0*PI/N*(i-3.0/4.0);
      x1[i-1]=-a[N-1]/N+r*cos(t); //アーバスの初期値
      x2[i-1]=r*sin(t);
   }

   double e,f1,f2,w1,w2,p1,p2,a1,a2,norm;
   do {
      e=0.0;

      for (i=0; i<N; i++) {
         f1=1.0; //分子 f(zi)、実部=f1,虚部=f2
         f2=0.0; //※a(0)=1
         for (j=N-1; j>=0; j--) { //ホーナー法 f(z)=( … ((z+a[n-1])*z+a[n-2])*z+a[n-3])* … +a[1])*z+a[0]
            w1=f1*x1[i]-f2*x2[i]; //f*z=(f1+i*f2)*(x1+i*x2)=(f1*x1-f2*x2)+i*(f1*x2+f2*x1)
            w2=f2*x1[i]+f1*x2[i];
            f1=w1+a[j]; //f=z+a[j]
            f2=w2;
         }

         p1=1.0; //分母 Π[j=1,N,i≠j](zi-zj)、実部=p1,虚部=p2
         p2=0.0;
         for (j=0; j<N; j++) {
           if (j!=i) {
               w1=p1*(x1[i]-x1[j])-p2*(x2[i]-x2[j]); //p*(zi-zj)
               w2=p1*(x2[i]-x2[j])+p2*(x1[i]-x1[j]);
               p1=w1; //p=(zi-zj)
               p2=w2;
            }
         }

         t=p1*p1+p2*p2; //分子÷分母 (f1+i*f2)/(p1+i*p2)=(f1+i*f2)(p1-i*p2)/(p1*p1+p2*p2)
         if (t==0.0) {
            printf("0では割れません。\n");
            return 1;
         }
         a1=(f1*p1+f2*p2)/t;
         a2=(f2*p1-f1*p2)/t;
         norm=sqrt(a1*a1+a2*a2);
         if (e<norm) e=norm; //最大値

         x1[i]-=a1; //k回目の近似根 zi[k+1]=zi[k]-f(zi[k])/Π[j=1,N,i≠j](zi[k]-zj[k])
         x2[i]-=a2;
      }
   } while (e>1.0e-10); //収束するまで ※調整が必要である

   for (i=0; i<N; i++) printf( "x%d  実部=%f  虚部=%f\n", i,x1[i],x2[i]);

   return 0;
}


実行結果

x0  実部=1.000000  虚部=0.000000
x1  実部=2.000000  虚部=-0.000000
x2  実部=3.000000  虚部=-0.000000
x3  実部=5.000000  虚部=-0.000000
x4  実部=8.000000  虚部=-0.000000
x5  実部=7.000000  虚部=-0.000000
x6  実部=6.000000  虚部=0.000000
x7  実部=4.000000  虚部=0.000000
続行するには何かキーを押してください . . .




 

Re: DK(Durand-Kerner)法について

 投稿者:山中和義  投稿日:2014年10月17日(金)15時13分50秒
  > No.3526[元記事へ]

tibitaさんへのお返事です。


complex.h を使ったサンプルです。 CPad 2.31 + Borland C++ Complier 5.5 で確認しました。



//DKA(Durand-Kerner-Aberth)法によるn次代数方程式f(x)=0の解法

#include <stdio.h>
#include <math.h>
#include <complex.h>

#define PI 3.14159265358979323846
#define N 8 //n次

int main(void)
{
   double a[N+1] = {40320,-109584,118124,-67284,22449,-4536,546,-36,1}; //係数
   complex<double> x[N]; //n個の根(実部、虚部)
   int i,j;

   for (i=0; i<=N; i++) a[i]/=a[N]; //x^n+a[n-1]/a[n]*x^(n-1)+ … + a[1]/a[n]*x+a[0]/a[n]の形へ

   double r=1.0, r0; //初期値を仮定する
   for (i=1; i<=N; i++) {
      r0=pow(fabs(N*a[i]),1.0/i);
      if (r<r0) r=r0;
   }
   for (i=0; i<N; i++) { //半径rの円に等間隔に配置する
      x[i]=-a[N-1]/N+r*exp(2.0*PI/N*((i+1)-3.0/4.0)*(0,1)); //アーバスの初期値
   }

   double e,norm;
   do {
      e=0.0;

      for (i=0; i<N; i++) {
         complex<double> f,p,t;

         f=1.0; //分子 f(zi) ※a(n)=1
         for (j=N-1; j>=0; j--)
            f=f*x[i]+a[j]; //ホーナー法 f(z)=( … ((z+a[n-1])*z+a[n-2])*z+a[n-3])* … +a[1])*z+a[0]

         p=1.0; //分母 Π[j=1,N,i≠j](zi-zj)
         for (j=0; j<N; j++)
           if (j!=i) p*=x[i]-x[j]; //p*(zi-zj)

         //分子÷分母 (f1+i*f2)/(p1+i*p2)=(f1+i*f2)(p1-i*p2)/(p1*p1+p2*p2)
         if (abs(p)==0.0) {
            printf("0では割れません。\n");
            return 1;
         }
         t=f/p;
         norm=abs(t);
         if (e<norm) e=norm; //最大値

         x[i]-=t; //k回目の近似根 zi[k+1]=zi[k]-f(zi[k])/Π[j=1,N,i≠j](zi[k]-zj[k])
      }
   } while (e>1.0e-10); //収束するまで ※調整が必要である

   for (i=0; i<N; i++) printf("x%d  実部=%f  虚部=%f\n", i,real(x[i]),imag(x[i]));

   return 0;
}


実行結果

D:\My Documents\C>test
x0  実部=3.000000  虚部=0.000000
x1  実部=4.000000  虚部=0.000000
x2  実部=7.000000  虚部=0.000000
x3  実部=2.000000  虚部=0.000000
x4  実部=1.000000  虚部=0.000000
x5  実部=6.000000  虚部=0.000000
x6  実部=5.000000  虚部=0.000000
x7  実部=8.000000  虚部=0.000000
-- Press any key to exit (Input "c" to continue) --


 

Re: DK(Durand-Kerner)法について

 投稿者:山中和義  投稿日:2014年10月17日(金)16時33分7秒
  > No.3529[元記事へ]


> //DKA(Durand-Kerner-Aberth)法によるn次代数方程式f(x)=0の解法
>
> #include <stdio.h>
> #include <math.h>
> #include <complex.h>
>
> #define PI 3.14159265358979323846
> #define N 8 //n次
>
> int main(void)
> {
>    double a[N+1] = {40320,-109584,118124,-67284,22449,-4536,546,-36,1}; //係数
>    complex<double> x[N]; //n個の根(実部、虚部)
>    int i,j;
>
>    for (i=0; i<=N; i++) a[i]/=a[N]; //x^n+a[n-1]/a[n]*x^(n-1)+ … + a[1]/a[n]*x+a[0]/a[n]の形へ
>
>    double r=1.0, r0; //初期値を仮定する
>    for (i=1; i<=N; i++) {
>       r0=pow(fabs(N*a[i]),1.0/i);
>       if (r<r0) r=r0;
>    }
>    for (i=0; i<N; i++) { //半径rの円に等間隔に配置する
>       x[i]=-a[N-1]/N+r*exp(2.0*PI/N*((i+1)-3.0/4.0)*(0,1)); //アーバスの初期値
>    }
>
>    double e,norm;
>    do {
>       e=0.0;
>


訂正します。



int main(void)
{
   double a[N+1] = {40320,-109584,118124,-67284,22449,-4536,546,-36,1}; //係数
   complex<double> Zi(0.0,1.0); //虚数単位
   complex<double> x[N]; //n個の根(実部、虚部)
   int i,j;

   for (i=0; i<=N; i++) a[i]/=a[N]; //x^n+a[n-1]/a[n]*x^(n-1)+ … + a[1]/a[n]*x+a[0]/a[n]の形へ

   double r=1.0, r0; //初期値を仮定する
   for (i=1; i<=N; i++) {
      r0=pow(fabs(N*a[i]),1.0/i);
      if (r<r0) r=r0;
   }
   for (i=0; i<N; i++) { //半径rの円に等間隔に配置する
      x[i]=-a[N-1]/N+r*exp(2.0*PI/N*((i+1)-3.0/4.0)*Zi); //アーバスの初期値
   }

   double e,norm;
   do {
      e=0.0;

  :
  :



実行結果

D:\My Documents\C>test
x0  実部=1.000000  虚部=0.000000
x1  実部=2.000000  虚部=-0.000000
x2  実部=3.000000  虚部=-0.000000
x3  実部=5.000000  虚部=0.000000
x4  実部=8.000000  虚部=-0.000000
x5  実部=7.000000  虚部=0.000000
x6  実部=6.000000  虚部=-0.000000
x7  実部=4.000000  虚部=-0.000000
-- Press any key to exit (Input "c" to continue) --


 

戻る