[C] Metody numeryczne

Projekty użytkowników forum zarówno sprzętowe, jak i związane z programowaniem w dowolnym języku.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[C] Metody numeryczne

Postautor: gaweł » niedziela 06 mar 2022, 21:23

Tematyka związana z Cyfrowym przetwarzaniem sygnałów siłą
rzeczy „dotyka” zagadnień numerycznych. To już samo w sobie
wykracza poza ramy samego DSP i każdy z tych elementów może
żyć własnym życiem. Jedynym elementem ich scalającym jest pewnego
rodzaju podobieństwo zastosowania. Z tego powodu zdecydowałem się
na utworzenie niezależnego wątku poświęconego szeroko pojętym
zagadnieniom numerycznym (rozwiązywania określonych problemów
matematycznych posiłkując się liczbami zmiennoprzecinkowymi
i językiem programowania → w tym konkretnym przypadku językiem C).
W sumie wszystko opiera się o wiedzę uniwersalną, wypracowaną przez
wysiłek wielu ludzi, chociaż w paru przypadkach kierowałem się własną
koncepcją oraz potrzebą. Z pewnością niektóre rozwiązania można
zrealizować inaczej (nie znaczy, że lepiej lub gorzej → po prostu inaczej).


Numeryczne poszukiwanie zer wielomianu

Potrzeba i koncepcja

W wielu zagadnieniach technicznych zachodzi potrzeba znalezienia pierwiastków wielomianów (rozwiązania równania z wielomianem). Zakładając, że wielomian stopnia n ma współczynniki rzeczywiste, to jego rozwiązanie będzie zawierać n rozwiązań (rzeczywistych oraz zespolonych). W przypadku rozwiązań zespolonych, będą one sprzężone.
Algorytm rozwiązania opiera się o metodę Newtona-Raphsona polegającej na iteracyjnym wyznaczeniu przybliżonej wartości pierwiastka równania,
W(x)=a0 + a1 x + a2 x2 + ... + an xn
gdzie a0, a1, a2, … an należą do zbioru liczb rzeczywistych R i przynajmniej an≠0.
Wyznaczenie pierwiastka wymaga określenia punktu startowego x0 (wartości niewiadomej, od której w sposób iteracyjny będzie poszukiwany pierwiastek). W każdej iteracji dla xk (aktualnej wartości niewiadomej) obliczana jest wartość wielomianu W(xk) i w przypadku, gdy W(xk)≠0 (równanie wielomianu nie jest spełnione), dokonuje się korekty jej wartości. Ideę pokazuje rysunek 1.
proot01.png

Początkowa wartość niewiadomej to x0, dla której obliczana jest wartość wielomianu. Jeżeli wartość wielomianu W(xk)≠0, to wystawiana jest styczna do krzywej wielomianu w punkcie x0. By znaleźć równanie stycznej niezbędna jest wartość pochodnej w tym punkcie W '(x0). Równanie stycznej to y = W '( x0 ) x + b, gdzie parametr b jest wyliczany ze współrzędnych punktu styku W( x0 )=W'( x0 )x + bb=W( x0 )-W '( x0 )x. Styczna przecina oś X w kolejnym punkcie, którego położenie x1 można wyznaczyć z równania stycznej: y=00=W '(x0)x1+W(x0)-W '(x0) x0 czyli x1=x0 - W(x0) / W '(x0. Wyznaczona wartość staje się kolejnym przybliżeniem do rozwiązania.
Dokładnie tą ideę można przenieść do przestrzeni liczb zespolonych, co sprowadza się do rozszerzenia znaczenia argumentu z liczb rzeczywistych do liczb zespolonych. Równocześnie zmienia się wartość funkcji z wartości rzeczywistych na wartości zespolone. Rozwiązaniem jest znalezienie takiego argumentu, gdzie wartość bezwzględna z wartości wielomianu dla tego argumentu jest zerowa.
Jeżeli w trójwymiarowym układzie współrzędnych przedstawić bezwzględną wartości wielomianu w funkcji części rzeczywistej oraz urojonej argumentów zespolonych, to przykładowo wielomian W(x)=x2+1 pokazuje rysunek 2.
proot02.png

Miejsca zerowe wielomianu (jego rozwiązania), to miejsca styku powierzchni wielomianu z płaszczyzną utworzoną przez osie Re z i Im z.
W rzeczywistości w obliczeniach numerycznych za wartość zerową przyjmuje się wartość dostatecznie bliską zera (-ξ < x < ξ ) →x=0). Algorytm Newtona-Raphsona znajduje jeden pierwiastek (jakiś), więc by określić kolejny koniecznością jest „pozbycie się” jego z równania. W tym celu wielomian jest dzielony przez dwumian i obniżany jest jego stopień. W myśl twierdzenia Bézouta wielomian ma obowiązek podzielić się bez reszty.
W przypadku gdy znaleziony pierwiastek xr jest rzeczywisty, wielomian jest dzielony przez dwumian (x-xr). W przypadku, gdy rozwiązaniem jest pierwiastek zespolony xr=xre + xim j, to przy istniejących założeniach dotyczących wartości współczynników przy kolejnych potęgach wielomianu, musi istnieć pierwiastek sprzężony xr=xre - xim j. Wielomian jest dzielony przez trójmian x2-2 xre x + xre2 + xim2 i jego stopień jest obniżony o dwa.
Po obniżeniu stopnia wielomianu ponownie jest poszukiwany kolejny pierwiastek wielomianu aż do osiągnięcia wielomianu drugiego stopnia lub pierwszego stopnia. W tych przypadkach wielomian jest rozwiązywany w oparciu o metody analityczne.
Istotnym elementem w całym zagadnieniu jest optymalne przyjęcie pierwszego przybliżenia rozwiązania (wybranie x0). Klasyczna metoda Newtona-Raphsona wymaga istnienia rozwiązania rzeczywistego i słabo sobie radzi z rozwiązaniami jedynie zespolonymi (rysunek 3). Uogólniona metoda do przestrzeni zespolonej dobrze zdaje egzamin w takich przypadkach (nie ma wstępnego założenia co do ilości pierwiastków czysto rzeczywistych lub zespolonych). Można przyjąć za pierwsze przybliżenie jednostkę urojoną x=j, jednak czysto urojona wartość początkowa z kolei słabo sobie radzi w sytuacjach, gdzie rozwiązania są czysto rzeczywiste (rysunek 4).
proot03.png

proot04.png

W takich przypadkach program wpada w oscylacje rozwiązania, gdzie kolejne przybliżenia oscylują wzdłuż osi rzeczywistej lub urojonej nie chcąc przejść na drugą oś.
Drogą różnych eksperymentów przyjęcie jako wartości startowej pierwszego przybliżenia liczby x0=1 + j sprawia, że zauważone problemy znikają.

Rozwiązania w programie

Algorytm poszukiwania miejsc zerowych metodą Newtona-Raphsona został zaimplementowany w języku C w oparciu o kompilator języka C/C++: DEV C++ version 5.11 (narzędzie dostępne na licencji GNU).
Na potrzeby określenia wielomianu oraz jego rozwiązania, w programie został zdefiniowany typ zmiennej.

Kod: Zaznacz cały

#define MaxPolynomialDegree        20

typedef double PolynomialCoeffArrayT [ MaxPolynomialDegree ] ;
//            PolynomialCoeffArrayT [ 0 ] - x^0
//            PolynomialCoeffArrayT [ 1 ] - x^1
//            PolynomialCoeffArrayT [ 2 ] - x^2
//            .....
//            PolynomialCoeffArrayT [ n ] - x^n

typedef double complex PolyRootsArrayT [ MaxPolynomialDegree - 1 ] ;

Typ PolynomialCoeffArrayT jest tablicą współczynników i reprezentuje wielomian (określa współczynniki przy kolejnych potęgach w wielomianie) z tym, że pod indeksem 0 zawiera się wyraz wolny oraz pod indeksem n występuje współczynnik przy n-tej potędze (rysunek 5). Typ PolyRootsArrayT dotyczy rozwiązań wielomianu i jest tablicą o wyrazach zespolonych.
proot09.png

Kompletne rozwiązanie posiłkuje się funkcjami i procedurami pomocniczymi, do których należą przykładowo procedury dzielenia wielomianu.
Do dzielenia wielomianu przez dwumian używana jest procedura DividePoly1.

Kod: Zaznacz cały

void DividePoly1 ( double         PolyCoeff [ ] ,
                   double         PolyRoot ,
                   unsigned short PolyDegree )
{
  signed short Loop ;
  PolynomialCoeffArrayT QuotientPoly ;
  /*------------------------------------------------------------------*/
  for ( Loop = PolyDegree - 1 ; Loop >= 0 ; Loop -- )
  {
    QuotientPoly [ Loop ] = PolyCoeff [ Loop + 1 ] ;
    PolyCoeff [ Loop ] = PolyCoeff [ Loop ] - PolyRoot * QuotientPoly [ Loop ];
  } /* for */ ;
  for ( Loop = 0 ; Loop < PolyDegree ; Loop ++ )
    PolyCoeff [ Loop ] = QuotientPoly [ Loop ] ;
} /* DividePoly1 */

Dzieli ona wejściowy wielomian określony przez tablicę PolyCoeff stopnia określonego przez parametr PolyDegree przez dwumian (x + PolyRoot). Wynik dzielenia znajduje się w tablicy PolyCoeff.
W przypadku dzielenia wielomianu przez trójmian, używana jest procedura DividePoly2.

Kod: Zaznacz cały

void DividePoly2 ( double         PolyCoeff [ ] ,
                   double         PolyRootX ,
                   double         PolyRoot ,
                   unsigned short PolyDegree )
{
 signed short Loop ;
 PolynomialCoeffArrayT QuotientPoly ;
 /*------------------------------------------------------------------*/
 for ( Loop = PolyDegree - 2 ; Loop >= 0 ; Loop -- )
 {
  QuotientPoly [ Loop ] = PolyCoeff [ Loop + 2 ] ;
  PolyCoeff [ Loop + 1 ] = PolyCoeff [ Loop + 1 ] - PolyRootX * QuotientPoly [ Loop ] ;
  PolyCoeff [ Loop ] = PolyCoeff [ Loop ] - PolyRoot * QuotientPoly [ Loop ] ;
 } /* for */ ;
 for ( Loop = 0 ; Loop < PolyDegree ; Loop ++ )
  PolyCoeff [ Loop ] = QuotientPoly [ Loop ] ;
} /* DividePoly2 */

Dzieli ona wejściowy wielomian określony przez tablicę PolyCoeff stopnia określonego przez parametr PolyDegree przez trójmian (x2 + PolyRootX x + PolyRoot). Wynik dzielenia znajduje się w tablicy PolyCoeff.
Funkcja do poszukiwania pierwiastka jest następująca.

Kod: Zaznacz cały

unsigned short LocateRootsNewtonRaphson ( double complex * PolyRoot ,
                                          double           PolyCoeff [ ] ,
                                          unsigned short   PolyDegree )

Dostaje on w parametrach dane opisujące wielomian (tablica współczynników PolyCoeff) oraz informacje dotyczące stopnia wielomianu (parametr PolyDegree). Wartość pierwiastka jest wynoszona na zewnątrz poprzez parametr PolyRoot (typu zespolonego). W przypadku pierwiastka rzeczywistego, zawiera się on w części rzeczywistej zmiennej, w przypadku rozwiązania zespolonego, w obu częściach (rzeczywistej oraz urojonej).
Wynikiem funkcji jest status operacji, jako jeden z następujących wariantów.

Kod: Zaznacz cały

#define LocateRoots_OK        0
#define LocateRoots_real      1
#define LocateRoots_complex   2
#define LocateRoots_fail      3
#define LocateRoots_overflow  4
#define LocateRoots_LoopLimit 5

Ich znaczenie jest następujące:
  • LocateRoots_OK – rozwiązanie jest kompletne (wynik funkcji PolyRootsNewtonRaphson),
  • LocateRoots_real – funkcja LocateRootsNewtonRaphson doiterowała się pierwiastka rzeczywistego,
  • LocateRoots_complex – funkcja LocateRootsNewtonRaphson doiterowała się pierwiastka zespolonego, podany jest jedynie jeden pierwiastek i należy sobie automatycznie dodać pierwiastek sprzężony,
  • LocateRoots_fail – wystąpił błąd wewnętrzny (nie powinno się zdarzyć),
  • LocateRoots_overflow – w poszukiwaniu pierwiastka doszło do dzielenia przez zero (normalnie nie powinno wystąpić),
  • LocateRoots_LoopLimit - funkcja LocateRootsNewtonRaphson wpadła w oscylacje i nie doiterowała się żadnego rozwiązania (limit iteracji jest określony przez stałą LoopLimit.
Rozwiązanie funkcji poszukiwania pierwiastka jest następujące.

Kod: Zaznacz cały

unsigned short LocateRootsNewtonRaphson ( double complex * PolyRoot ,
                                          double           PolyCoeff [ ] ,
                                          unsigned short   PolyDegree )
{
  double Delta ;
  double complex Root ;
  double complex CurrArg ;
  double complex PolyValue ;
  double complex DerivValue ;
  unsigned short LoopCounter ;
  /*------------------------------------------------------------------*/
  if ( PolyDegree < 1 )
    return ( LocateRoots_fail ) ;
  if ( PolyDegree == 1 )
  {
    if ( IsZero ( PolyCoeff [ 1 ] ) )
      return ( LocateRoots_overflow ) ;
    __real__ Root = - PolyCoeff [ 0 ] / PolyCoeff [ 1 ] ;
    __imag__ Root = 0.0 ;
    * PolyRoot = Root ;
    return ( LocateRoots_real ) ;
  } /* if */ ;
  if ( PolyDegree == 2 )
  {
    if ( IsZero ( PolyCoeff [ 2 ] ) )
      return ( LocateRoots_overflow ) ;
    Delta = PolyCoeff [ 1 ] * PolyCoeff[1] - 4.0 * PolyCoeff [ 2 ] * PolyCoeff [0] ;
    if ( Delta >= 0 )
    {
      __real__ Root = ( -PolyCoeff[1] + sqrt ( Delta )) / ( 2.0 * PolyCoeff [2] ) ;
      __imag__ Root = 0.0 ;
      * PolyRoot = Root ;
      return ( LocateRoots_real ) ;
    } /* if ... */
    else
    {
      __real__ Root = - PolyCoeff [ 1 ] / ( 2.0 * PolyCoeff [ 2 ] ) ;
      __imag__ Root = - sqrt ( - Delta ) / ( 2.0 * PolyCoeff [ 2 ] ) ;
      * PolyRoot = Root ;
      return ( LocateRoots_complex ) ;
    } /* if ... else */ ;
    return ( LocateRoots_fail ) ;
  } /* if */ ;
  CurrArg = 1.0 + I ;
  LoopCounter = 0 ;
  for ( ; ; )
  {
    PolyValue = FunctionValue ( PolyCoeff , PolyDegree , CurrArg ) ;
    if ( IsZero ( __real__ PolyValue ) && IsZero ( __imag__ PolyValue ) )
    {
      if ( IsZero ( __imag__ CurrArg ) )
      {
        __imag__ CurrArg = 0.0 ;
        * PolyRoot = CurrArg ;
        return ( LocateRoots_real ) ;
      } /* if ... */
      else
      {
        * PolyRoot = CurrArg ;
        return ( LocateRoots_complex ) ;
      } /* if ... else */ ;
    } /* if */ ;
    DerivValue = DerivativeValue ( PolyCoeff , PolyDegree , CurrArg ) ;
    Root = CurrArg ;
    if ( IsZero ( __real__ DerivValue ) && IsZero ( __imag__ DerivValue ) )
      CurrArg = 0.9 * Root ;
    else
      CurrArg = Root - PolyValue / DerivValue ;
    LoopCounter ++ ;
    if ( LoopCounter > LoopLimit )
      return LocateRoots_LoopLimit ;
  } /* for */ ;
} /* LocateRootsNewtonRaphson */

W powyższej funkcji używana jest pomocnicza funkcja do obliczenia wartości wielomianu oraz wartości pochodnej dla określonego argumentu. Jej postać pokazuje:

Kod: Zaznacz cały

double complex FunctionValue ( double         PolyCoeff [ ] ,
                               unsigned short PolyDegree ,
                               double complex Argument )
{
  double complex FValue ;
  signed short Loop ;
  /*------------------------------------------------------------------*/
  FValue = PolyCoeff [ PolyDegree ] + 0.0 * I ;
  for ( Loop = PolyDegree ; Loop > 0 ; Loop -- )
  {
    FValue = FValue * Argument ;
    __real__ FValue = __real__ FValue + PolyCoeff [ Loop - 1 ] ;
  } /* for */ ;
  return ( FValue ) ;
} /* FunctionValue */

Idea obliczeń jest oparta o następujące operacje realizowane w pętli:
  • FValue jest podstawiona na wartość współczynnika przy najwyższej potędze an (FV=an),
    wykonane są pętli następujące operacje (arg jest argumentem funkcji → parametr wywołania),
    FV= an arg + an-1
    FV=( an arg + an-1) arg + an-2

    FV=((( an arg + an-1) arg + an-2) arg + an-3 ... ) arg + a0
Do obliczania wartości pochodnej używana jest również powyższa funkcja, jej realizację pokazuje:

Kod: Zaznacz cały

double complex DerivativeValue ( double         PolyCoeff [ ] ,
                                 unsigned short PolyDegree ,
                                 double complex Argument )
{
  signed short Loop ;
  PolynomialCoeffArrayT Derivative ;
  /*------------------------------------------------------------------*/
  for ( Loop = PolyDegree ; Loop > 0 ; Loop -- )
    Derivative [ Loop - 1 ] = ( double ) ( Loop ) * PolyCoeff [ Loop ] ;
  return ( FunctionValue ( Derivative , PolyDegree - 1 , Argument ) ) ;
} /* DerivativeValue */

Finalnie do znalezienia wszystkich pierwiastków wielomianu używana jest funkcja PolyRootsNewtonRaphson .

Kod: Zaznacz cały

unsigned short PolyRootsNewtonRaphson ( double complex PolyRootArr [ ] ,
                                        double         PolyCoeff [ ] ,
                                        unsigned short PolyDegree )
{
  double complex PolyRoot ;
  unsigned short RootsIndex ;
  /*------------------------------------------------------------------*/
  RootsIndex = 0 ;
  for ( ; ; )
  {
    switch ( LocateRootsNewtonRaphson ( & PolyRoot , PolyCoeff , PolyDegree ) )
    {
      case LocateRoots_real      :
        DividePoly1 ( PolyCoeff , - __real__ PolyRoot , PolyDegree ) ;
        PolyDegree -- ;
        PolyRootArr [ RootsIndex ++ ] = PolyRoot ;
        break ;
      case LocateRoots_complex   :
        DividePoly2 ( PolyCoeff , - 2.0 * __real__ PolyRoot ,
                      __real__ PolyRoot * __real__ PolyRoot +
                      __imag__ PolyRoot * __imag__ PolyRoot , PolyDegree ) ;
        PolyDegree -- ;
        PolyDegree -- ;
        PolyRootArr [ RootsIndex ++ ] = PolyRoot ;
        __imag__ PolyRoot = - __imag__ PolyRoot ;
        PolyRootArr [ RootsIndex ++ ] = PolyRoot ;
        break ;
      case LocateRoots_fail      :
        return ( LocateRoots_fail ) ;
      case LocateRoots_overflow  :
        return ( LocateRoots_overflow ) ;
      case LocateRoots_LoopLimit :
        return ( LocateRoots_LoopLimit ) ;
    } /* switch */ ;
    if ( PolyDegree == 0 )
      return ( LocateRoots_OK ) ;
  } /* for */ ;
} /* PolyRootsNewtonRaphson */

Jej zadaniem jest obniżanie stopnia wielomianu aż do uzyskania końcowego rozwiązania.
Przykład użycia pokazuje:

Kod: Zaznacz cały

int main ( int argc ,
           char * argv [ ] )
{
  unsigned short Loop ;
  unsigned short PDegree ;
  double complex FValue ;
  PolynomialCoeffArrayT PolyCopy ;
  /*------------------------------------------------------------------*/
  OutFile = fopen ( "proots.txt" , "w" ) ;
  fprintf ( OutFile , "*** Program poszukujacy miejsc zerowych wielomianu ***\n\n" ) ;
  Polynomial [ 0 ] = 0.0 ;
  Polynomial [ 1 ] = 3.0 ;
  Polynomial [ 2 ] = 3.0 ;
  Polynomial [ 3 ] = 3.0 ;
  PDegree = 3 ;
  for ( Loop = 0 ; Loop <= PDegree ; Loop ++ )
    PolyCopy [ Loop ] = Polynomial [ Loop ] ;
  PrintPoly ( Polynomial , PDegree ) ;
  PolyRootsNewtonRaphson ( PolyRoots , Polynomial , PDegree ) ;
  fprintf ( OutFile , "\n\n\nWyniki:\n" );
  for ( Loop = 0 ; Loop < PDegree ; Loop ++ )
  {
    if ( IsZero ( __imag__ PolyRoots [ Loop ] ) )
      fprintf ( OutFile , "Pierw.rzecz.:(%.10f)" , __real__ PolyRoots [ Loop ] ) ;
    else
      fprintf ( OutFile , "Pierw.zespo.: (%.10f)+(%.10f j)" ,
                        __real__ PolyRoots [ Loop ] , __imag__ PolyRoots [ Loop ] ) ;
    FValue = FunctionValue ( PolyCopy , PDegree , PolyRoots [ Loop ] ) ;
    fprintf ( OutFile , " wart.wielom.: (%.10f)+(%.10f j)\n" ,
                        __real__ FValue , __imag__ FValue ) ;
  } /* for */ ;
  fclose ( OutFile ) ;
  return 0 ;
} /* main */


Dla tropicieli:
polyroots.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony niedziela 06 mar 2022, 22:44 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [C] Metody numeryczne

Postautor: gaweł » niedziela 06 mar 2022, 22:30

Testy

Wielomian trzeciego stopnia W(x)=3 x3 + 3 x2 + 3 x. Z samej matematyki wiadomo, że wielomian ma pierwiastek rzeczywisty o wartości xr=0, gdzie po podzieleniu wielomianu przez dwumian powstaje równanie kwadratowe, które rozwiązane jest już analitycznie. Znalezienie pierwszego pierwiastka odbywa się iteracyjnie. Program wykonał następujące korki (rysunek 6).

Kod: Zaznacz cały

z=(1.000000000000) + (1.000000000000) j
z=(0.493150684932) + (0.684931506849) j
z=(0.128939925373) + (0.454260320345) j
z=(-0.130555361235) + (0.202522098160) j
z=(-0.018883920719) + (-0.066162696604) j
z=(-0.004059894129) + (0.002557146026) j
z=(0.000009944756) + (-0.000020762241) j
z=(-0.000000000332) + (-0.000000000413) j
Program uznał rozwiązanie za rzeczywiste.

proot05.png


Wielomian W(x)=x4 – 1000256 x2 + 256000000=(x2 - 1000000)(x2-256), który ma cztery pierwiastki rzeczywiste. Poszukując pierwszego pierwiastka program wykonał następujące iteracje (rysunek 7).

Kod: Zaznacz cały

z=(1.000000000000) + (1.000000000000) j
z=(64.483877060934) + (-63.483365322969) j
z=(33.525998851792) + (-30.502295126433) j
z=(18.887981727876) + (-13.321878999557) j
z=(13.974850261990) + (-3.467330690192) j
z=(15.615845403004) + (0.406646482683) j
z=(15.999171128695) + (-0.009972029580) j
z=(15.999996916607) + (0.000000514169) j
z=(16.000000000000) + (-0.000000000000) j
z=(16.000000000000) + (-0.000000000000) j
Pierwiastek rzeczywisty: 16.0000000000

proot06.png

Poszukiwanie drugiego pierwiastka równania generuje następujące działania (rysunek 8). Po znalezieniu tego pierwiastka, równanie staje się trójmianem i dalej jest rozwiązywane analitycznie.

Kod: Zaznacz cały

z=(1.000000000000) + (1.000000000000) j
z=(-16.000507991782) + (-0.000644039913) j
z=(-16.000000000005) + (0.000000000021) j
z=(-16.000000000000) + (-0.000000000000) j
Pierwiastek rzeczywisty: -16.0000000000

proot07.png


Bardziej złożony test dotyczy następującego wielomianu:
W ( x ) = x19 – x18 – 2 x17 -3 x16 – 4 x15 -1 x14 + x13 + x12 – x10 – 2 x9 -3 x8 – x7 -5 x6 +5 x5 + 4 x4 + 3x3
Zbieżność poszczególnych rozwiązań pokazuje rysunek 9 (w przypadku rozwiązań zespolonych, rysunek nie zawiera drogi dojścia dla obu pierwiastków sprzężonych: tylko jednego z nich). Wykonane cykle iteracyjne dla poszczególnych pierwiastków prezentuje listing.

Kod: Zaznacz cały

z=(1.000000000000) + (1.000000000000) j
z=(0.935969108178) + (0.938112496763) j
z=(0.873072634201) + (0.878249040683) j
z=(0.809767875888) + (0.819273699983) j
z=(0.743255339013) + (0.759313313851) j
z=(0.668569563352) + (0.695087690415) j
z=(0.578482538094) + (0.620777779887) j
z=(0.470965001361) + (0.528889474814) j
z=(0.362157480197) + (0.425032971281) j
z=(0.265450232144) + (0.328656054859) j
z=(0.185147816770) + (0.247258897069) j
(. . . )
z=(0.009744779570) + (0.018607517159) j
z=(0.006458392886) + (0.012458821400) j
z=(0.004288501472) + (0.008329723617) j
z=(0.002851362364) + (0.005563729064) j
z=(0.001897501511) + (0.003713850973) j
z=(0.001263483496) + (0.002477987802) j
z=(0.000841646888) + (0.001652919249) j
z=(0.000560797437) + (0.001102358271) j
Pierwiastek zespolony: 0.0005607974 +0.0011023583 j
Pierwiastek zespolony: 0.0005607974 -0.0011023583 j
z=(1.000000000000) + (1.000000000000) j
z=(0.926565959298) + (0.929377390442) j
z=(0.853884190533) + (0.860961558121) j
z=(0.778789335479) + (0.792629715389) j
z=(0.694275732942) + (0.720301393968) j
z=(0.584079608681) + (0.635077539058) j
z=(0.418406804427) + (0.514629348112) j
z=(0.214739171261) + (0.341069360735) j
z=(0.032619411572) + (0.183806018854) j
z=(-0.046420853944) + (0.029550688207) j
z=(0.000523842443) + (-0.003521375898) j
z=(-0.001134125643) + (-0.000015432897) j
z=(-0.001121229281) + (0.000000000530) j
Pierwiastek rzeczywisty: -0.0011212293
z=(1.000000000000) + (1.000000000000) j
z=(0.935969108178) + (0.938112496763) j
z=(0.873072634201) + (0.878249040683) j
z=(0.809767875888) + (0.819273699983) j
z=(0.743255339013) + (0.759313313851) j
z=(0.668569563352) + (0.695087690415) j
z=(0.578482538094) + (0.620777779887) j
z=(0.470965001361) + (0.528889474814) j
z=(0.362157480197) + (0.425032971281) j
z=(0.265450232144) + (0.328656054859) j
z=(0.185147816770) + (0.247258897069) j
z=(0.123830779420) + (0.180742520012) j
z=(0.080858455314) + (0.128303316082) j
z=(0.052485779905) + (0.088994722692) j
z=(0.034186346394) + (0.060810609512) j
z=(0.022394026622) + (0.041177117621) j
z=(0.014743984201) + (0.027728585989) j
z=(0.009744779570) + (0.018607517159) j
z=(0.006458392886) + (0.012458821400) j
z=(0.004288501472) + (0.008329723617) j
z=(0.002851362364) + (0.005563729064) j
z=(0.001897501511) + (0.003713850973) j
z=(0.001263483496) + (0.002477987802) j
z=(0.000841646888) + (0.001652919249) j
z=(0.000560797437) + (0.001102358271) j
Pierwiastek zespolony: 0.0005607974 +0.0011023583 j
Pierwiastek zespolony: 0.0005607974 -0.0011023583 j
z=(1.000000000000) + (1.000000000000) j
z=(0.926565959298) + (0.929377390442) j
z=(0.853884190533) + (0.860961558121) j
z=(0.778789335479) + (0.792629715389) j
z=(0.694275732942) + (0.720301393968) j
z=(0.214739171261) + (0.341069360735) j
z=(0.032619411572) + (0.183806018854) j
z=(-0.046420853944) + (0.029550688207) j
z=(0.000523842443) + (-0.003521375898) j
z=(-0.001134125643) + (-0.000015432897) j
z=(-0.001121229281) + (0.000000000530) j
Pierwiastek rzeczywisty: -0.0011212293
z=(1.000000000000) + (1.000000000000) j
z=(0.920757720140) + (0.924015495744) j
z=(0.841819320700) + (0.850260862501) j
z=(0.758344839816) + (0.775686245569) j
z=(0.657844519342) + (0.693785125817) j
z=(0.502816232656) + (0.587924878874) j
z=(0.196689037017) + (0.400436884040) j
z=(-0.246184829998) + (0.236227927414) j
z=(-0.459190379347) + (0.697712680167) j
z=(-0.366348028947) + (0.572245410108) j
z=(-0.332340930331) + (0.530378259956) j
z=(-0.329205694816) + (0.528821599694) j
z=(-0.329193346684) + (0.528827541174) j
Pierwiastek zespolony: -0.3291933467 +0.5288275412 j
Pierwiastek zespolony: -0.3291933467 -0.5288275412 j
z=(1.000000000000) + (1.000000000000) j
z=(0.912217677988) + (0.910448520924) j
z=(0.824054828882) + (0.822513977450) j
z=(0.726689028142) + (0.728093979116) j
z=(0.590127066367) + (0.598378106627) j
z=(0.286987627838) + (0.201906298001) j
z=(1.581448318574) + (-0.572462369135) j
z=(1.448787542907) + (-0.511893256666) j
z=(1.326316328297) + (-0.461805881053) j
z=(1.214658643689) + (-0.421764833788) j
z=(1.113577522963) + (-0.393193421109) j
z=(1.022246718897) + (-0.381648870433) j
z=(0.944333830016) + (-0.403262844133) j
z=(0.935518206581) + (-0.467204917641) j
z=(0.950027927047) + (-0.452911907915) j
z=(0.951120187478) + (-0.454968812025) j
z=(0.951118966820) + (-0.454937835387) j
z=(0.951118971058) + (-0.454937831785) j
Pierwiastek zespolony: 0.9511189711 -0.4549378318 j
Pierwiastek zespolony: 0.9511189711 +0.4549378318 j
z=(1.000000000000) + (1.000000000000) j
z=(0.862632545787) + (0.918836425170) j
z=(0.728824926606) + (0.859381017413) j
z=(0.598990736163) + (0.832385864665) j
z=(0.485989443290) + (0.859480538005) j
z=(0.456819269014) + (0.947707770242) j
z=(0.491892587443) + (0.933683486297) j
z=(0.489163121417) + (0.939232509504) j
z=(0.489334835649) + (0.939294901694) j
z=(0.489334677405) + (0.939294875746) j
Pierwiastek zespolony: 0.4893346774 +0.9392948757 j
Pierwiastek zespolony: 0.4893346774 -0.9392948757 j
z=(1.000000000000) + (1.000000000000) j
z=(0.806775170246) + (0.856649097832) j
z=(0.607737799888) + (0.724087198156) j
z=(0.384285098463) + (0.598267302673) j
z=(0.102107335388) + (0.492366799585) j
z=(-0.251961203678) + (0.455778975021) j
z=(-0.662597575216) + (0.492511467227) j
z=(-1.016825165461) + (0.602357919838) j
z=(-0.894066814278) + (0.559511457994) j
z=(-0.768403449601) + (0.658081704563) j
z=(-0.854080794040) + (0.650149754292) j
z=(-0.841263025013) + (0.665870150489) j
z=(-0.842687058166) + (0.666233162870) j
z=(-0.842680295664) + (0.666236927051) j
Pierwiastek zespolony: -0.8426802957 +0.6662369271 j
Pierwiastek zespolony: -0.8426802957 -0.6662369271 j
z=(1.000000000000) + (1.000000000000) j
z=(0.766744552998) + (0.804110655695) j
z=(0.511426704987) + (0.612025038168) j
z=(0.151883712336) + (0.383877061469) j
z=(-0.711285289184) + (0.256237458987) j
z=(-1.191690336082) + (-0.214740953686) j
z=(-1.138070590973) + (-0.213307394151) j
z=(-1.128736590544) + (-0.221320178175) j
z=(-1.129040277495) + (-0.221752685474) j
z=(-1.129039929673) + (-0.221751802063) j
Pierwiastek zespolony: -1.1290399297 -0.2217518021 j
Pierwiastek zespolony: -1.1290399297 +0.2217518021 j
z=(1.000000000000) + (1.000000000000) j
z=(0.715444879254) + (0.713698634949) j
z=(0.369936161362) + (0.357621208034) j
z=(1.096516456098) + (-1.411967801540) j
z=(0.850353947452) + (-1.107617541540) j
z=(0.570713301930) + (-0.843362871330) j
z=(0.149832879954) + (-0.592656433576) j
z=(-0.643371740626) + (-1.700467134413) j
z=(-0.493171175054) + (-1.485944908409) j
z=(-0.377351380338) + (-1.320806233778) j
z=(-0.293265449620) + (-1.185552317650) j
z=(-0.259190098453) + (-1.036448855540) j
z=(-0.413746511019) + (-1.033083130277) j
z=(-0.368651923113) + (-1.031614728988) j
z=(-0.362252351797) + (-1.032477338640) j
z=(-0.362138073835) + (-1.032525109832) j
z=(-0.362138046074) + (-1.032525145315) j
Pierwiastek zespolony: -0.3621380461 -1.0325251453 j
Pierwiastek zespolony: -0.3621380461 +1.0325251453 j
z=(1.000000000000) + (1.000000000000) j
z=(0.706598304955) + (0.463701612430) j
z=(0.820797759293) + (-0.068590762278) j
z=(0.884375633387) + (0.001405984404) j
z=(0.884477975512) + (-0.000000044175) j
z=(0.884478278314) + (0.000000000000) j
Pierwiastek rzeczywisty: 0.8844782783
z=(1.000000000000) + (1.000000000000) j
z=(-0.030350311675) + (0.311787097419) j
z=(1.234416663027) + (1.375180785802) j
z=(0.633182435020) + (0.569669375763) j
z=(-1.359323389164) + (1.332921080016) j
z=(-0.580678481060) + (1.029457030030) j
z=(-0.053783749987) + (1.028752810667) j
z=(0.051554451294) + (1.239595505223) j
z=(0.018163512584) + (1.231289666123) j
z=(0.018198078162) + (1.232053537135) j
z=(0.018197861386) + (1.232053225837) j
Pierwiastek zespolony: 0.0181978614 +1.2320532258 j
Pierwiastek zespolony: 0.0181978614 -1.2320532258 j

proot08.png

Ostatni pierwiastek wyliczony jest metodą analityczną. Finalnie równanie ma następujący zestaw pierwiastków:

Kod: Zaznacz cały

Pierw.zespo.: (0.0005607974)+(0.0011023583 j)
Pierw.zespo.: (0.0005607974)+(-0.0011023583 j)
Pierw.rzecz.: (-0.0011212293)
Pierw.zespo.: (-0.3291933467)+(0.5288275412 j)
Pierw.zespo.: (-0.3291933467)+(-0.5288275412 j)
Pierw.zespo.: (0.9511189711)+(-0.4549378318 j)
Pierw.zespo.: (0.9511189711)+(0.4549378318 j)
Pierw.zespo.: (0.4893346774)+(0.9392948757 j)
Pierw.zespo.: (0.4893346774)+(-0.9392948757 j)
Pierw.zespo.: (-0.8426802957)+(0.6662369271 j)
Pierw.zespo.: (-0.8426802957)+(-0.6662369271 j)
Pierw.zespo.: (-1.1290399297)+(-0.2217518021 j)
Pierw.zespo.: (-1.1290399297)+(0.2217518021 j)
Pierw.zespo.: (-0.3621380461)+(-1.0325251453 j)
Pierw.zespo.: (-0.3621380461)+(1.0325251453 j)
Pierw.rzecz.: (0.8844782783)
Pierw.zespo.: (0.0181978614)+(1.2320532258 j)
Pierw.zespo.: (0.0181978614)+(-1.2320532258 j)
Pierw.rzecz.: (2.5243215726)
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [C] Metody numeryczne

Postautor: gaweł » piątek 25 mar 2022, 03:49

Generator liczb losowych

W wielu zagadnieniach technicznych przydatnym jest generator liczb losowych. Za jego pomocą można przykładowo symulować sygnał zakłócający i badać metody jego oczyszczania oraz opracowywać metody eliminacji. Jedną z istotnych cech generatora liczb losowych jest jego kolor. To dosyć niepoważnie brzmiące określenie ma głęboki sens fizyczny. Wyprodukowany sygnał można poddać analizie częstotliwościowej. Jeżeli zawartość harmonicznych będzie dosyć równomiernie rozłożona, to można mówić o białym generatorze szumu (zawiera każdą możliwą częstotliwość). Jeżeli tak nie jest, to generator produkuje przykładowo kolor różowy. Uzyskanie generatora o określonym kolorze nie należy do czynności trywialnych. Jest sporo wręcz sprzecznych ze sobą wymagań, jak choćby nieskończenie wielki okres (z tą nieskończonością to chyba lekkie przegięcie), czyli po ilu losowaniach powtórzą się wartości. W literaturze można znaleźć wiele przykładowych generatorów i uważam, że nie warto traciś sił i środków na wymyślanie własnego. Poniższy jest zaczerpnięty z "Numerical Recipes in C". Typowe generatory dają wartości jako liczby zmiennoprzecinkowe z zakresu 0 .. 1.

Kod: Zaznacz cały

#include <stdio.h>

#define IA 16807
#define IM 2147483647
#define AM (1.0/IM)
#define IQ 127773
#define IR 2836
#define NTAB 32
#define NDIV (1+(IM-1)/NTAB)
#define EPS 1.2e-7
#define RNMX (1.0-EPS)

float Random1 ( long * idum )
{
  int Loop ;
  long Coof ;
  static long iy = 0 ;
  static long iv [ NTAB ] ;
  float temp ;
  /*-------------------------------------------------------------------------*/
  if ( * idum <= 0 || ! iy )
  {
    if ( - ( * idum ) < 1 )
      * idum = 1 ;
    else
      *idum = - ( * idum ) ;
    for ( Loop = NTAB + 7 ; Loop >= 0 ; Loop -- )
    {
      Coof = ( * idum ) / IQ ;
      * idum = IA * ( * idum - Coof * IQ ) - IR * Coof ;
      if ( * idum < 0 )
        * idum += IM ;
      if ( Loop < NTAB )
        iv [ Loop ] = * idum ;
    } /* for */ ;
    iy = iv [ 0 ] ;
  } /* if */ ;
  Coof = ( * idum ) / IQ ;
  * idum = IA * ( * idum - Coof * IQ ) - IR * Coof ;
  if ( * idum < 0 )
    * idum += IM ;
  Loop = iy / NDIV ;
  iy = iv [ Loop ] ;
  iv [ Loop ] = * idum ;
  temp = AM * iy ;
  if ( temp > RNMX )
    temp = RNMX ;
  return temp ;
} /* Random1 */

#define TabSize 1024

int main(int argc, char *argv[])
{
  FILE * OutFile ; 
  long idum ;
  float RandVal ;
  unsigned short Loop ;
  /*-------------------------------------------------------------------------*/
  idum = 0 ;
  OutFile = fopen ( "rxd.txt" , "w" ) ;
  fprintf ( OutFile , "* * * Program generator liczb losowych * * *\n\n" ) ;
  for ( Loop = 0 ; Loop < TabSize ; Loop ++ )
  {
    RandVal = Random1 ( & idum ) ;
    fprintf ( OutFile , "probka.%d =  %.8f ;\n" , Loop + 1 , RandVal ) ;
  } /* for */ ;
  fclose ( OutFile ) ;
   return 0;
}

Uruchomienie tego programu generuje plik tekstowy z generowanymi wartościami, jego fragment (lektura całości jest raczej monotonna :lol: )

Kod: Zaznacz cały

* * * Program generator liczb losowych * * *

probka.1 =  0.41599935 ;
probka.2 =  0.09196489 ;
probka.3 =  0.75641048 ;
probka.4 =  0.52970022 ;
probka.5 =  0.93043649 ;
probka.6 =  0.38350207 ;
probka.7 =  0.65391898 ;
probka.8 =  0.06684224 ;
probka.9 =  0.72266042 ;
probka.10 =  0.67114937 ;
probka.11 =  0.38341564 ;
probka.12 =  0.63163471 ;
probka.13 =  0.88470715 ;
probka.14 =  0.51941639 ;
probka.15 =  0.65151858 ;
probka.16 =  0.23777443 ;
probka.17 =  0.26245299 ;
probka.18 =  0.76219803 ;
probka.19 =  0.75335586 ;
probka.20 =  0.90920812 ;
probka.21 =  0.07268588 ;
probka.22 =  0.27270997 ;
probka.23 =  0.89765626 ;
probka.24 =  0.27490684 ;
probka.25 =  0.51629198 ;
probka.26 =  0.35926497 ;

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [C] Metody numeryczne

Postautor: gaweł » niedziela 03 kwie 2022, 02:11

Numeryczne rozwiązywanie liniowych
układów równań


W wielu zagadnieniach technicznych czynnością wykonywaną niejako w tle, jest rozwiązanie układu równań liniowych. To element składowy wielu działań, taki mały trybik w większej maszynerii. Równania można zapisać w klasycznej formie:
mat01_form00.png

lub w formie macierzowej:
mat01_form01.png

gdzie A jest macierzą kwadratową n*n układu składającą się ze wszystkich współczynników układu równań,
X jest poszukiwaną macierzą kolumnową
B jest macierzą kolumnową współczynników (tych po drugiej stronie równań).
mat01_form02.png

mat01_form03.png

mat01_form04.png

By rozwiązać ten układ równań, należy go pomnożyć obustronnie przez macierz odwrotną do macierzy A (pamiętając, że działania należy wykonać we właściwej kolejności, gdyż nie są przemienne).
mat01_form05.png

Ta prosta koncepcja zawiera w sobie kilka mniejszych, że wymienię przykładowo mnożenie macierzy kwadratowej przez macierz kolumnową czy odwracanie macierzy. Tu z kolei elementem podstawowym jest obliczenie wyznacznika macierzy. Zagłębiając się w cały ten ciąg operacji podstawowym elementem jest obliczenie wyznacznika. W przypadku macierzy 2*2, wynik jest nawet prosty do zapamiętania:
mat01_form06.png

w przypadku 3*3, to już staje się bardziej złożone (wiadomo, im bardziej w las, tym więcej drzew), ale jeszcze do opanowania:
mat01_form07.png

W bardziej złożonych sytuacjach to zaczynają się już „schody”. Jednak tu znanych jest kilka możliwości i naszym zadaniem jest odpowiedni wybór: spośród iluś możliwości wybieramy jedną. Zdecydowałem się na metodę Laplace'a, gdyż jest samopodobną (rozkłada się na ciąg działań o identycznej budowie jednak obejmujących mniejsze struktury).
Przykładowo dla macierzy 4*4 rozkłada się na obliczenie czterech wyznaczników macierzy 3*3 powstałych poprzez wykreślenie jednej kolumny i odpowiednich wierszy. Ja wybrałem w implementacji programowej pierwszą kolumnę i odpowiednie wiersze (rys 1).
matrix00.png

W przypadku ręcznego rachowania to można dokonywać pewnych optymalizacji (przykładowo wybrać takie wiersze/kolumny, gdzie występuje dużo zer). Z punktu widzenia programu to ma mniejsze znaczenie, gdyż wtedy koniecznością staje się stworzenie bardziej złożonych algorytmów, które pozwolą na jakąś tam optymalizację (coś jak sztuczna inteligencja?). Narobić się w programie, by praktycznie nic nie uzyskać, to może nie jest najlepsza koncepcja. Dla procka to jedna rybka, co robi, umie mnożyć, to niech mnoży (robi to wystarczająco dobrze). No więc implementacja jest bez wodotrysków.
Oczywiście obliczenie wyznaczników o większych rozmiarach sprowadza się do obliczenie iluś tam wyznaczników macierzy mniejszych, które z kolei rekurencyjnie składają się z obliczenia kolejnych wyznaczników macierzy mniejszych itd.

Program

Utworzona jest następująca struktura do „trzymania” macierzy:

Kod: Zaznacz cały

#define MaxMatrixDimension       32
typedef double MatrixT [ MaxMatrixDimension ] [ MaxMatrixDimension ] ;

i funkcja obliczająca wartość wyznacznika:

Kod: Zaznacz cały

double Determinant ( double  Matrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ,
                     unsigned short MatrixDegree )
{
  double TmpMatrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ;
  double DeterminantPart ;
  double Factor ;
  unsigned short RowIndex ;
  unsigned short OutRowNo ;
  unsigned short OutColumnNo ;
  unsigned short RowLoop ;
  unsigned short ColumnLoop ;
/*------------------------------------------------------------------*/
  if ( MatrixDegree == 1 )
  {
    return ( Matrix [ 0 ] [ 0 ] ) ;
  } /* if */ ;
  if ( MatrixDegree == 2 )
  {
    return ( Matrix[0][0] * Matrix[1][1] -  Matrix[0][1] * Matrix[1][0] ) ;
  } /* if */ ;
  if ( MatrixDegree == 3 )
  {
    return ( Matrix[0][0] * Matrix[1][1] * Matrix[2][2] +
             Matrix[0][1] * Matrix[1][2] * Matrix[2][0] +
             Matrix[0][2] * Matrix[1][0] * Matrix[2][1] -
             Matrix[0][2] * Matrix[1][1] * Matrix[2][0] -
             Matrix[0][0] * Matrix[1][2] * Matrix[2][1] -
             Matrix[0][1] * Matrix[1][0] * Matrix[2][2] ) ;
  } /* if */ ;
  DeterminantPart = 0.0 ;
  for ( RowIndex = 0 ; RowIndex < MatrixDegree ; RowIndex ++ )
  {
    Factor = Matrix [ RowIndex ] [ 0 ] * Delta ( RowIndex , 0 ) ;
    if ( MatrixIsZero ( Factor ) )
      continue ;
    OutRowNo = 0 ;
    OutColumnNo = 0 ;
    for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
    {
      if ( RowLoop == RowIndex )
        continue ;
      for ( ColumnLoop = 1 ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
      {
        TmpMatrix [ OutRowNo ] [ ColumnLoop - 1 ] = Matrix [ RowLoop ] [ ColumnLoop ] ;
      } /* for */ ;
      OutRowNo ++ ;
    } /* for */ ;
    DeterminantPart = DeterminantPart+Factor*Determinant ( TmpMatrix,MatrixDegree-1 ) ;
  } /* for */ ;
  return ( DeterminantPart ) ;
} /* Determinant */

Przykład użycia i testy:

Kod: Zaznacz cały

unsigned short SetMatrix1 ( void )
{
  /*----------------------------------------------------------------------*/
  Matrix [ 0 ] [ 0 ] = 1.0 ;
  Matrix [ 0 ] [ 1 ] = 2.0 ;
  Matrix [ 0 ] [ 2 ] = 3.0 ;
  Matrix [ 0 ] [ 3 ] = 4.0 ;
  Matrix [ 0 ] [ 4 ] = 5.0 ;
  Matrix [ 0 ] [ 5 ] = 6.0 ;
  Matrix [ 1 ] [ 0 ] = 11.0 ;
  Matrix [ 1 ] [ 1 ] = 12.0 ;
  Matrix [ 1 ] [ 2 ] = 13.0 ;
  Matrix [ 1 ] [ 3 ] = 14.0 ;
  Matrix [ 1 ] [ 4 ] = 15.0 ;
  Matrix [ 1 ] [ 5 ] = 16.0 ;
  Matrix [ 2 ] [ 0 ] = 21.0 ;
  Matrix [ 2 ] [ 1 ] = 22.0 ;
  Matrix [ 2 ] [ 2 ] = 23.0 ;
  Matrix [ 2 ] [ 3 ] = 24.0 ;
  Matrix [ 2 ] [ 4 ] = 25.0 ;
  Matrix [ 2 ] [ 5 ] = 26.0 ;
  Matrix [ 3 ] [ 0 ] = 31.0 ;
  Matrix [ 3 ] [ 1 ] = 32.0 ;
  Matrix [ 3 ] [ 2 ] = 33.0 ;
  Matrix [ 3 ] [ 3 ] = 34.0 ;
  Matrix [ 3 ] [ 4 ] = 35.0 ;
  Matrix [ 3 ] [ 5 ] = 36.0 ;
  Matrix [ 4 ] [ 0 ] = 41.0 ;
  Matrix [ 4 ] [ 1 ] = 42.0 ;
  Matrix [ 4 ] [ 2 ] = 43.0 ;
  Matrix [ 4 ] [ 3 ] = 44.0 ;
  Matrix [ 4 ] [ 4 ] = 45.0 ;
  Matrix [ 4 ] [ 5 ] = 46.0 ;
  Matrix [ 5 ] [ 0 ] = 51.0 ;
  Matrix [ 5 ] [ 1 ] = 52.0 ;
  Matrix [ 5 ] [ 2 ] = 53.0 ;
  Matrix [ 5 ] [ 3 ] = 54.0 ;
  Matrix [ 5 ] [ 4 ] = 55.0 ;
  Matrix [ 5 ] [ 5 ] = 56.0 ;
  return ( 6 ) ;
} /* SetMatrix1 */


Kod: Zaznacz cały

int main ( int argc , char** argv )
{
  unsigned short MatrixDegree ;
  double MatrixDeterminant ;
  FILE * OctaveFile ; 
  /*----------------------------------------------------------------------*/
  OutFile = fopen ( "matrix.txt" , "w" ) ;
  fprintf ( OutFile , "* * * Program obrobki macierzy * * *\n" ) ;
  MatrixDegree = SetMatrix1 ( ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  MatrixDeterminant = Determinant ( Matrix , MatrixDegree ) ;
  fprintf ( OutFile , "Wyznacznik macierzy = %.6f\n\n" ,  MatrixDeterminant ) ;
.
.
.
  fclose ( OutFile ) ;
  return ( 0 ) ;
} /* main */

Puszczenie programu dla takich danych generuje następujący rezultat:

Kod: Zaznacz cały

* * * Program obrobki macierzy * * *

Macierz
   +1.000000    +2.000000    +3.000000    +4.000000    +5.000000    +6.000000
  +11.000000   +12.000000   +13.000000   +14.000000   +15.000000   +16.000000
  +21.000000   +22.000000   +23.000000   +24.000000   +25.000000   +26.000000
  +31.000000   +32.000000   +33.000000   +34.000000   +35.000000   +36.000000
  +41.000000   +42.000000   +43.000000   +44.000000   +45.000000   +46.000000
  +51.000000   +52.000000   +53.000000   +54.000000   +55.000000   +56.000000
******************************************

Wyznacznik macierzy = 0.000000


Dla tropicieli:
matrix.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [C] Metody numeryczne

Postautor: gaweł » niedziela 03 kwie 2022, 13:16

Numeryczne rozwiązywanie liniowych
układów równań - cd


Rozwiązanie układu równań wymaga znalezienia macierzy odwrotnej. Jest to jakaś tam złożona operacja, gdzie również istnieją dwie drogi postępowania: metoda dopełnień algebraicznych (w ogromnej mierze opartej na obliczaniu wyznaczników) oraz metoda eliminacji Gaussa. Ponieważ mamy już stworzone narzędzia (liczenie wyznaczników), implementacja będzie opierała się o dopełnienia algebraiczne.
Dopełnienie określonego elementu macierzy to wyznacznik macierzy powstałej z wykreślenia z macierzy wiersza i kolumny wziętej z odpowiednim znakiem (wynikającym z parzystości/nieparzystości położenia tego elementu).
Nie będę rozwodził się na temat szczegółów metody, gdyż ta wiedza jest powszechnie dostępna i można samodzielnie z nią się zapoznać. Pierwszym krokiem, jaki należy wykonać, to obliczyć wyznacznik macierzy. Może się tak zdarzyć, że wyznacznik macierzy wyjdzie zero, to wtedy jest już pozamiatane: nie jest możliwe obliczenie macierzy odwrotnej a sam układ równań jest nierozwiązywalny.
Opierając się o wyżej opisaną funkcję obliczania wyznaczników, implementacja algorytmu znajdowania macierzy odwrotnej jest następująca:

Kod: Zaznacz cały

unsigned short InverseMatrix ( double   Matrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ,
                               unsigned short MatrixDegree )
{
  double MatDeterminant ;
  double ResultMatrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ;
  unsigned RowLoop ;
  unsigned ColumnLoop ;
/*------------------------------------------------------------------*/
  MatDeterminant = Determinant ( Matrix , MatrixDegree ) ;
  if ( MatrixIsZero ( MatDeterminant ) )
    return ( InverseDetZero ) ;
  for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
  {
    for ( ColumnLoop = 0 ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
    {
      ResultMatrix [ RowLoop ] [ ColumnLoop ] = Delta ( RowLoop , ColumnLoop ) *
                   MatrixCofactor ( Matrix , MatrixDegree , RowLoop , ColumnLoop ) /
                   MatDeterminant ;
    } /* for */ ;
  } /* for */ ;
  TransposeSquareMatrix ( ResultMatrix , MatrixDegree ) ;
  for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
  {
    for ( ColumnLoop = 0 ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
    {
      Matrix [ RowLoop ] [ ColumnLoop ] = ResultMatrix [ RowLoop ] [ ColumnLoop ] ;
    } /* for */ ;
  } /* for */ ;
  return ( InverseOK ) ;
} /* InverseMatrix */

gdzie pomocnicze elementy są następujące (ustalenie znaku dopełnienia):

Kod: Zaznacz cały

static double Delta ( unsigned short RowNo ,
                      unsigned short ColumnNo )
{
/*------------------------------------------------------------------*/
  if ( ( ( RowNo + ColumnNo ) & 0x01 ) == 0 )
    return ( 1.0 ) ;
  else
    return ( -1.0 ) ;
} /* Delta */

dopełnienie algebraiczne macierzy (usunięcie z macierzy niepożądanych elementów):

Kod: Zaznacz cały

static double MatrixCofactor ( double         Matrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ,
                               unsigned short MatrixDegree ,
                               unsigned short WithoutRow ,
                               unsigned short WithoutColumn )
{
  double TmpMatrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ;
  unsigned RowLoop ;
  unsigned ColumnLoop ;
  unsigned OutRow ;
  unsigned OutColumn ;
/*------------------------------------------------------------------*/
  OutRow = 0 ;
  for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
  {
    OutColumn = 0 ;
    if ( RowLoop == WithoutRow )
      continue ;
    for ( ColumnLoop = 0 ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
    {
      if ( ColumnLoop == WithoutColumn )
        continue ;
      TmpMatrix [ OutRow ] [ OutColumn ++ ] = Matrix [ RowLoop ] [ ColumnLoop ] ;
    } /* for */ ;
    OutRow ++ ;
  } /* for */ ;
  return ( Determinant ( TmpMatrix , MatrixDegree - 1 ) ) ;
} /* MatrixCofactor */

oraz transponowanie macierzy (zamiana elementów miejscami):

Kod: Zaznacz cały

void TransposeSquareMatrix ( double         Matrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ,
                             unsigned short MatrixDegree )
{
  double TempValue ;
  unsigned short RowLoop ;
  unsigned short ColumnLoop ;
/*------------------------------------------------------------------*/
  if ( MatrixDegree <= 1 )
    return ;
  if ( MatrixDegree == 1 )
  {
    TempValue = Matrix [ 0 ] [ 1 ] ;
    Matrix [ 0 ] [ 1 ] = Matrix [ 1 ] [ 0 ] ;
    Matrix [ 1 ] [ 0 ] = TempValue ;
    return ;
  } /* if */ ;
  for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
  {
    for ( ColumnLoop = RowLoop ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
    {
      if ( ColumnLoop == RowLoop )
        continue ;
      TempValue = Matrix [ RowLoop ] [ ColumnLoop ] ;
      Matrix [ RowLoop ] [ ColumnLoop ] = Matrix [ ColumnLoop ] [ RowLoop ] ;
      Matrix [ ColumnLoop ] [ RowLoop ] = TempValue ;
    } /* for */ ;
  } /* for */ ;
} /* TransposeSquareMatrix */

Do pełni szczęścia konieczna jest jeszcze operacja mnożenia macierzy kwadratowej przez macierz kolumnową. Dostarczam gotowego rozwiązania:

Kod: Zaznacz cały

void MultSquareMatrixByVector ( double OutVector [ ] ,
                                double Matrix [ MaxMatrixDimension ] [ MaxMatrixDimension ] ,
                                double InVector [ ] ,
                                unsigned short MatrixDegree )
{
  double TempValue ;
  unsigned short RowLoop ;
  unsigned short ColumnLoop ;
/*------------------------------------------------------------------*/
  for ( RowLoop = 0 ; RowLoop < MatrixDegree ; RowLoop ++ )
  {
    TempValue = 0.0 ;
    for ( ColumnLoop = 0 ; ColumnLoop < MatrixDegree ; ColumnLoop ++ )
    {
      TempValue += Matrix [ RowLoop ] [ ColumnLoop ] * InVector [ ColumnLoop ] ;
    } /* for */ ;
    OutVector [ RowLoop ] = TempValue ;
  } /* for */ ;
} /* MultSquareMatrixByVector */

Pozostało sprawdzić co z tego wyszło. Kawałek programu testowego:

Kod: Zaznacz cały

int main ( int argc , char** argv )
{
  unsigned short MatrixDegree ;
  unsigned short Status ;
  unsigned short Loop ;
  double MatrixDeterminant ;
  double InVector [ MaxMatrixDimension ] ;
  double OutVector [ MaxMatrixDimension ] ;
  FILE * OctaveFile ; 
  /*----------------------------------------------------------------------*/
  OutFile = fopen ( "matrix.txt" , "w" ) ;
  fprintf ( OutFile , "* * * Program obrobki macierzy * * *\n" ) ;
  MatrixDegree = SetMatrix1 ( ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  MatrixDeterminant = Determinant ( Matrix , MatrixDegree ) ;
  fprintf ( OutFile , "Wyznacznik macierzy = %.6f\n\n" ,  MatrixDeterminant ) ;
  MatrixDegree = SetMatrix2 ( ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  TransposeSquareMatrix ( Matrix , MatrixDegree ) ;
  fprintf ( OutFile , "Macierz transponowana\n" ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  MatrixDegree = SetMatrix2 ( ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  fprintf ( OutFile , "Macierz odwrotna\n" ) ;
  Status = InverseMatrix ( Matrix , MatrixDegree ) ;
  switch ( Status )
  {
    case InverseOK                :
      fprintf ( OutFile , "Macierz odwrotna została obliczona\n\n" ) ;
      PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
      break ;
    case InverseDetZero           :
      fprintf ( OutFile , "Macierz odwrotna nie istnieje\n\n" ) ;
      break ;
  } /* switch */ ;
  MatrixDegree = SetMatrix2 ( ) ;
  InVector [ 0 ] = 19.0 ;
  InVector [ 1 ] = 57.0 ;
  InVector [ 2 ] = 95.0 ;
  InVector [ 3 ] = 133.0 ;
  InVector [ 4 ] = 171.0 ;
  InVector [ 5 ] = 209.0 ;
  PrintEqual ( OutFile , Matrix , InVector , MatrixDegree ) ;
  Status = InverseMatrix ( Matrix , MatrixDegree ) ;
  fprintf ( OutFile , "Macierz odwrotna\n" ) ;
  PrintMatrix ( OutFile , Matrix , MatrixDegree ) ;
  MultSquareMatrixByVector ( OutVector , Matrix , InVector , MatrixDegree ) ;
  for ( Loop = 0 ; Loop < MatrixDegree ; Loop ++ )
    fprintf ( OutFile , "x%d = %+12.6f\n" , Loop + 1 , OutVector [ Loop ] ) ;
  fclose ( OutFile ) ;
  return ( 0 ) ;
} /* main */

W konkretnym przypadku rozwiązywany jest układ równań:
  • -1 x1 + 2 x2 + 3 x3 + 4 x4 + 5 x5 + 6 x6 = 19
  • 11 x1 - 12 x2 + 13 x3 + 14 x4 + 15 x5 + 16 x6 = 57
  • 21 x1 + 22 x2 - 23 x3 + 24 x4 + 25 x5 + 26 x6 = 95
  • 31 x1 + 32 x2 + 33 x3 - 34 x4 + 35 x5 + 36 x6 = 133
  • 41 x1 + 42 x2 + 43 x3 + 44 x4 - 45 x5 + 46 x6 = 171
  • 51 x1 + 52 x2 + 53 x3 + 54 x4 + 55 x5 - 56 x6 = 209
Zostaje uruchomiony program, który wyprodukował następujący wynik:

Kod: Zaznacz cały

* * * Program obrobki macierzy * * *

Macierz
   +1.000000    +2.000000    +3.000000    +4.000000    +5.000000    +6.000000
  +11.000000   +12.000000   +13.000000   +14.000000   +15.000000   +16.000000
  +21.000000   +22.000000   +23.000000   +24.000000   +25.000000   +26.000000
  +31.000000   +32.000000   +33.000000   +34.000000   +35.000000   +36.000000
  +41.000000   +42.000000   +43.000000   +44.000000   +45.000000   +46.000000
  +51.000000   +52.000000   +53.000000   +54.000000   +55.000000   +56.000000
******************************************

Wyznacznik macierzy = 0.000000


Macierz
   -1.000000    +2.000000    +3.000000    +4.000000    +5.000000    +6.000000
  +11.000000   -12.000000   +13.000000   +14.000000   +15.000000   +16.000000
  +21.000000   +22.000000   -23.000000   +24.000000   +25.000000   +26.000000
  +31.000000   +32.000000   +33.000000   -34.000000   +35.000000   +36.000000
  +41.000000   +42.000000   +43.000000   +44.000000   -45.000000   +46.000000
  +51.000000   +52.000000   +53.000000   +54.000000   +55.000000   -56.000000
******************************************

Macierz transponowana

Macierz
   -1.000000   +11.000000   +21.000000   +31.000000   +41.000000   +51.000000
   +2.000000   -12.000000   +22.000000   +32.000000   +42.000000   +52.000000
   +3.000000   +13.000000   -23.000000   +33.000000   +43.000000   +53.000000
   +4.000000   +14.000000   +24.000000   -34.000000   +44.000000   +54.000000
   +5.000000   +15.000000   +25.000000   +35.000000   -45.000000   +55.000000
   +6.000000   +16.000000   +26.000000   +36.000000   +46.000000   -56.000000
******************************************


Macierz
   -1.000000    +2.000000    +3.000000    +4.000000    +5.000000    +6.000000
  +11.000000   -12.000000   +13.000000   +14.000000   +15.000000   +16.000000
  +21.000000   +22.000000   -23.000000   +24.000000   +25.000000   +26.000000
  +31.000000   +32.000000   +33.000000   -34.000000   +35.000000   +36.000000
  +41.000000   +42.000000   +43.000000   +44.000000   -45.000000   +46.000000
  +51.000000   +52.000000   +53.000000   +54.000000   +55.000000   -56.000000
******************************************

Macierz odwrotna
Macierz odwrotna została obliczona


Macierz
   -0.152565    +0.024456    +0.010413    +0.005457    +0.002924    +0.001386
   +0.058654    -0.035260    +0.004135    +0.003334    +0.002924    +0.002675
   +0.046098    +0.005622    -0.017877    +0.003241    +0.002924    +0.002731
   +0.041667    +0.005345    +0.003766    -0.011497    +0.002924    +0.002751
   +0.039402    +0.005204    +0.003717    +0.003192    -0.008187    +0.002761
   +0.038027    +0.005118    +0.003687    +0.003182    +0.002924    -0.006161
******************************************


uklad rownan
   -1.000 * x1   +2.000 * x2   +3.000 * x3   +4.000 * x4   +5.000 * x5   +6.000 * x6  =  +19.000
  +11.000 * x1  -12.000 * x2  +13.000 * x3  +14.000 * x4  +15.000 * x5  +16.000 * x6  =  +57.000
  +21.000 * x1  +22.000 * x2  -23.000 * x3  +24.000 * x4  +25.000 * x5  +26.000 * x6  =  +95.000
  +31.000 * x1  +32.000 * x2  +33.000 * x3  -34.000 * x4  +35.000 * x5  +36.000 * x6  = +133.000
  +41.000 * x1  +42.000 * x2  +43.000 * x3  +44.000 * x4  -45.000 * x5  +46.000 * x6  = +171.000
  +51.000 * x1  +52.000 * x2  +53.000 * x3  +54.000 * x4  +55.000 * x5  -56.000 * x6  = +209.000
******************************************

Macierz odwrotna

Macierz
   -0.152565    +0.024456    +0.010413    +0.005457    +0.002924    +0.001386
   +0.058654    -0.035260    +0.004135    +0.003334    +0.002924    +0.002675
   +0.046098    +0.005622    -0.017877    +0.003241    +0.002924    +0.002731
   +0.041667    +0.005345    +0.003766    -0.011497    +0.002924    +0.002751
   +0.039402    +0.005204    +0.003717    +0.003192    -0.008187    +0.002761
   +0.038027    +0.005118    +0.003687    +0.003182    +0.002924    -0.006161
******************************************

x1 =    +1.000000
x2 =    +1.000000
x3 =    +1.000000
x4 =    +1.000000
x5 =    +1.000000
x6 =    +1.000000


Dla tropicieli:
matrix.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „DIY”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 5 gości