PC, czyli preconditionery

Jedną z zalet pakietu PETSc jest łatwość używania preconditionerów przy rozwiązywaniu układów równań liniowych. PETSc ma wbudowane wiele gotowych preconditionerów; co więcej, można łatwo dodawać własne. Idea preconditioningu dla układu równań liniowych:

polega na zastąpieniu tego układu równoważnym, mnożąc układ wyjściowy przez odwrotność pewnej macierzy B, zwanej preconditionerem:

.

Jeżeli teraz zaczniemy rozwiązywać powyższy układ metodą iteracyjną, np. metodą iteracji prostej to otrzymujemy następujące wzory na kolejne przybliżenia rozwiązania:

.

Oznaczając  mamy:

,

czyli w każdym kroku iteracyjnym mnożymy wektor residualny przez odwrotność macierzy B: po prostu rozwiązujemy układ z macierzą preconditionera. Od macierzy B żądamy:

Obecnie poszukiwanie coraz efektywniejszych preconditionerów, zwłaszcza dla najnowocześniejszych architektur komputerowych (np. masywnie równoległych), stanowi jeden z ważniejszych obszarów zainteresowań analizy numerycznej, o bezpośrednim zastosowaniu w praktycznych symulacjach komputerowych.
Do kontroli preconditionera służy obiekt typu PC, który jest częścią obiektu KSP. Intuicyjnie wydaje się to oczywiste: konkretna metoda iteracyjna praktycznie zawsze musi być stosowana z preconditionerem, na przykład możemy wybrać metodę sprzężonych gradientów z preconditionerem Schwarza. W tym rozdziale zostaną omówione podstawowe zasady korzystania z PC, następnie typy preconditionerów zawarte w pakiecie oraz, dokładnie, metoda tworzenia własnych preconditionerów.
W świetle tego co przed chwilą stwierdziliśmy, każdy utworzony obiekt typu SLES zawiera już obiekt PC. Aby uzyskać do niego bezpośredni dostęp należy skojarzyć go ze zmienną typu PC przy pomocy funkcji SLESGetPC(SLES sles,PC *pc);
SLES sles;
PC pc;
...
SLESCreate(MPI_COMM_WORLD,&sles);
SLESGetPC(sles,&pc);
...
Teraz możemy już działać na obiekcie PC, stowarzyszonym z obiektem typu SLES, który zostanie uwzględniony w trakcie rozwiązywania zadania, czyli po wywołaniu funkcji SLESSolve(). Jak widać, nie musimy najpierw wydobywać ze SLESu KSP, aby potem dotrzeć do PC. Formalnie PC jest częścią KSP związanego ze SLESem, a nie samego SLESu, ale ze względu na powszechność stosowania preconditionerów w trakcie rozwiązywania równań liniowych autorzy pakietu napisali także funkcję SLESGetPC().
Najczęściej wykorzystywaną operacją na PC jest PCSetType(PC pc,PCType type) - funkcja ustawiająca typ preconditionera. Pierwszy argument to obiekt PC dla którego ustawiamy typ, drugim jest jeden z predefiniowanych typów:
 
Opis preconditionera
Nazwa stałej
Opcja z linii komend
Jacobi  PCJACOBI  jacobi 
Blokowy Jacobi  PCBJACOBI  bjacobi 
Blokowy GaussSeidel (tylko sekwencyjny)  PCBGS  bgs 
SOR (i SSOR)  PCSOR  sor 
SOR z trickiem Eisenstat'a  PCEISENSTAT  eisenstat 
Niekompletny rozkład Choleskiego  PCICC  icc 
Niekompletny rozkład LU  PCILU  ilu 
Addytywna metoda Schwarza  PCASM  asm 
Eliminacja Gaussa LU  PCLU  lu 
Bez preconditionera  PCMINUS_ONE  minus_one 
Zdefiniowany przez użytkownika  PCSHELL  shell 
 
Preconditioner PCLU jest po prostu metodą eliminacji Gaussa bez wyboru elementu głównego - jest to metoda bezpośrednia. Uczynienie jej preconditionerem pozwoliło na wspólny schemat wywołania metod iteracyjnych i metod bezpośrednich. Preconditionera PCLU używa się z metodą KSPPREONLY, czyli bez metody iteracyjnej - stosujemy jedynie preconditioner. Zatem, jeśli chcemy rozwiązać układ równań bezpośrednio, można to zrobić tak:
SLES sles;
KSP ksp;
PC pc;
...
SLESCreate(MPI_COM_WORLD,&sles);
...
SLESGetKSP(sles,&ksp);
KSPSetType(ksp,KSPPREONLY);
SLESGetPC(sles,&pc); 
PCSetType(pc,LU);
...
SLESSolve(sles);
Jeżeli natomiast chcemy jako preconditioner wykorzystać niekompletny rozkład Choleskiego, to trzeba zrobić tak:
...
SLESCreate(MPI_COMM_WORLD,&sles);
SLESGetPC(sles,&pc);
PCSetType(pc,PCICC);
...
Zmieniliśmy tylko preconditioner, a metoda iteracyjna pozostaje bez zmian. Każdy z typów preconditionera ma zbiór sobie właściwych funkcji, służących do ustawiania parametrów właściwych tylko dla niego. Na przykład, dla addytywnej metody Schwarza mamy funkcje:
 
Funkcja
Opis
PCASMGetSubSLES()  Wydobywa z preconditionera tablicę SLESów służących do rozwiązywania zadania w podobszarach. 
PCASMSetLocalSubdomains()  Ustawia ile obszarów obsługuje procesor wywołujący tę funkcję. 
PCASMSetOverlap()  Ustawia szerokość zakładki między obszarami. 
PCASMSetTotalSubdomains()  Ustawia całkowitą liczbę podobszarów. 
PCASMSetType()  Ustawia rodzaj metody Schwarza - sposób w jaki dane będą przekazywane między procesorami. 
 
Bardzo skrócony opis dotyczący funkcji regulujących konkretne preconitionery można znaleźć w manualu, szczegóły dotyczące składni wszystkich procedur - w man pages.

Podsumowanie

Ustawić preconditioner według własnego uznania można na dwa sposoby:
Drugim sposobem jest ustawienie preconditionera z poziomu kodu źródłowego funkcją PCSetType(). W tym celu należy:

Tworzenie własnych preconditionerów

W PETSc mamy możliwość łatwego budowania nowych preconditionerów dla konkretnych metod iteracyjnych. Do tworzenia własnych preconditionerów służy typ preconditionera PCSHELL. Jak sama nazwa wskazuje, będzie on definiowany analogicznie jak macierze powłokowe. Dlatego wraz z takim preconditionerem trzeba zdefiniować sposób, w jaki działa on na wektor residualny, funkcją PCShellSetApply(PC pc, int (*wlasny_pc)(void*,Vec,Vec),void *ptr). Pierwszy argument tej funkcji to zmienna typu PC obsługująca preconditioner. Drugi to wskaźnik do funkcji napisanej przez użytkownika działającej na wektor residualny. Trzeci argument to wskaźnik do zmiennej potrzebnej funkcji wlasny_pc (może być PETSC_NULL, chodzi o zostawienie pola manewru dla takich implementacji, które potrzebują własnych danych). Funkcja wlasny_pc() musi mieć trzy argumenty. Pierwszy to wskaźnik do kontekstu używanego przez funkcję, może być PETSC_NULL. Druga i trzecia zmienna to odpowiednio wektory: residualny, otrzymywany przez preconditioner, i wynik działania na wektorze residualnym.
Tak więc, aby wstawić do SLESu własny preconditioner należy:

Przykłady

Przykład trywialny

Ten program włącza własny "preconditioner" do SLESu. W charakterze preconditionera użyjemy macierzy B=I. Nie jest to oczywiście macierz, która będzie w jakikolwiek poprawiała własności spektralne układu, ale za to mnożenie przez B-1 jest szczególnie tanie... polegać będzie po prostu na skopiowaniu wektora. Za każdym razem, gdy zostanie wywołana funkcja SLESSolve() wektor residualny będzie przechodził przez funkcję Pierwszy_pc (i nic z nim się nie będzie dzialo - funkcja VecCopy()). Funkcja PCShellSetName(pc,"Mój pierwszy preconditioner") nadaje nazwę preconditionerowi powłokowemu. Przy wypisywaniu informacji na temat SLESu pojawi się właśnie ta nazwa. Pliki common.h i common.c są podane w przykładzie podsumowującym, znajduje się tam także Makefile służący do skompilowania przykładów.
 
przyklad8_1.c

#include "common.h"
static char help[] ="Preconditioner trywialny\n";

/*************************************************
        Działanie preconditionera
*************************************************/

int Pierwszy_pc(void *ctx,Vec v0,Vec v1)
{
 VecCopy(v0,v1);
 return(0);
}

/*************************************************
            Program glowny
*************************************************/

int main(int argc,char **args)
{
 PetscInitialize(&argc,&args,PETSC_NULL,help);
 Przygotuj_zadanie(N,&sles,&A,&u0,&u,&f);

/*ustawienie preconditionera powłokowego */
 SLESGetPC(sles,&pc);
 PCSetType(pc,PCSHELL);
 PCShellSetApply(pc,Pierwszy_pc,PETSC_NULL);
 PCShellSetName(pc,"Mój pierwszy preconditioner");

/*rozwiązanie zadania */
 Rozwiaz(sles);

/*Zwalnianie pamięci*/
 Zwolnij_pamiec(sles,A,u,u0,f);
 PetscFinalize();
}
Po uruchomieniu mamy:

hydra:/usr/home/bin/> przyklad
Iteracje = 351
 Blad    = 6.364062e-04

hydra:/usr/home/bin/>
Aby zobaczyć efekt PCShellSetName() trzeba uruchomić program z opcją -sles_view. Oczywiście trywialny preconditioner ma trywialną postać macierzową macierz  jest w tym wypadku macierzą identyczności.

Preconditioner Richardsona.

Ten preconditioner stosuje przeskalowanie wektora w stylu poprawki Richardsona, to znaczy, jako preconditioner B wybieramy macierz:
,

dla odpowiednio dobranej wartości parametru . Jak wiadomo, najlepszy parametr metody Richardsona uzyskuje się, korzystając z najmniejszej i największej wartości własnej macierzy (opis metody Richardsona można znaleźć w [6]). Dlatego jako kontekst podane zostaje oszacowanie na spektrum macierzy. Podobnie jak w poprzednim przypadku, pliki common.h i common.c są podane na końcu tego rozdziału.

przyklad8_2.c

#include "common.h"
static char help[] ="Preconditioner Richardsona\n";

/***************************************************
          Kontekst dla preconditionera
***************************************************/

typedef struct {
   double lambdamin;
double lambdamax;} Kontekst;

Kontekst k_pc;

/***************************************************
          Dzialanie preconditionera
***************************************************/

int Richardson_pc(void *ctx,Vec v0,Vec v1)
{
 Scalar val=1;
 Kontekst *kn=(Kontekst*)ctx;
 val=2/(kn->lambdamin+kn->lambdamax);
 VecCopy(v0,v1);
 VecScale(&val,v1);
 return(0);
}

/**************************************************
          Program glowny
**************************************************/

main(int argc,char **args)
{
 PetscInitialize(&argc,&args,PETSC_NULL,help);
 Przygotuj_zadanie(N,&sles,&A,&u0,&u,&f);
 SLESGetPC(sles,&pc);
 PCSetType(pc,PCSHELL);
 PCShellSetApply(pc,Richardson_pc,(void*)&k_pc);
 PCShellSetName(pc,"Preconditioner Richardsona");
 k_pc.lambdamin=1/N;
 k_pc.lambdamax=1;
 Rozwiaz(sles);
 Zwolnij_pamiec(sles,A,u0,u,f);
 PetscFinalize();
}
Po uruchomieniu mamy:

hydra:/usr/home/bin/> przyklad
Iteracje = 80
 Blad    = 4.366761e-04

hydra:/usr/home/bin/>

Preconditioner Jacobi'ego.

Teraz zaimplementujemy preconditioner Jacobi'ego, mnożący wektor residualny przez odwrotność diagonali operatora. Macierz preconditionera ma zatem następującą postać:

gdzie aii oznaczają elementy diagonalne macierzy zadania A. W implementacji tego preconditionera skorzystamy więc ze zmiennej kontekstowej: jako kontekst podamy główną diagonalę macierzy zadania. Zwróćmy uwagę na funkcję PrzygotujPreconditioner() - wykonuje on podobną pracę, co w standardowym przypadku funkcja PCSetUp().

przyklad8_3.c

#include "common.h"
static char help[] ="Preconditioner Jacobi'ego";

/***********************************************
    Inicjalizacja kontekstu preconditionera
***********************************************/

int PrzygotujPreconditioner(Mat A,Vec *k_pc)
{
 int m,n;
 MatGetSize(A,&m,&n);
 If(m!=n)
 {
  PetscPrintf(MPI_COMM_WORLD,"Hej!! Macierz nie jest kwadratowa\n");
 }
 VecCreate(MPI_COMM_WORLD,PETSC_DECIDE,m,k_pc);
 MatGetDiagonal(A,(*k_pc));
 return(0);
}
/***********************************************
          Dzialanie preconditionera
***********************************************/

int wlasny_pc(void *ctx,Vec v0,Vec v1)
{
 Vec *k_pc=(Vec*)ctx;
/* Dzialanie preconditionerem Jacobiego */
 VecPointwiseDivide(v0,*k_pc,v1);
 return(0);
}

/***********************************************
              Program glowny
***********************************************/

int main(int argc,char **args)
{
 Vec diag;

 PetscInitialize(&argc,&args,PETSC_NULL,help);
 Przygotuj_zadanie(N,&sles,&A,&u0,&u,&f);
 SLESGetPC(sles,&pc);
 PCSetType(pc,PCSHELL);
 PrzygotujPreconditioner(A,&diag);
 PCShellSetApply(pc,wlasny_pc,&diag);
 PCShellSetName(pc,"Preconditioner Jacobi'ego");
 Rozwiaz(sles);
 PetscFinalize();
}
Po uruchomieniu mamy:

hydra:/usr/home/bin/> przyklad
Iteracje = 63
 Blad    = 4.364761e-04

hydra:/usr/home/bin/>
Jak widać, macierz B spełnia postulaty preconditionera: jest łatwo odwracalna (jak każda macierz diagonalna) i jednocześnie poprawia współczynnik uwarunkowania zadania, co widać po liczbie wykonanych iteracji.

Przykład podsumowujący: addytywna metoda Schwarza z grubą siatką

Ten przykład demonstruje zastosowanie preconditionera addytywną metodę Schwarza z grubą siatką. W PETSc jest gotowy preconditioner Schwarza, ale działający jedynie na siatkach jednorodnych na prostokącie. Gdy chcemy użyć preconditionera z gruba siatką, musimy napisać własny.
 
przyklad8_4.c

#include "common.h"
static char help[] ="Addytywny preconditioner Schwarza z grubą siatką
                                 Marcin Bojko";

/* Tutaj definiujemy strukture, która bedzie kontekstem preconditionera */
typedef struct {
 Mat I,Ag;               /* macierz interpolacji i zadania na grubej siatce */
 int rozmiar,mg;         /* mg to rozmiar grubej siatki*/
 int m,M,zakl;           /* M to ilość podobszarów w jednym kierunku */
                         /* wszystkich podobszarów będzie M*M */
 IS *boxy;               /* tu będzie składowana dekompozycja */
 Vec fg,ug;              /* pomocnicze wektory zadania na grubej siatce */
 Scalar value[3],val;
 SLES slesg;             /* SLES dla zadania na gubej siatce */
 PC pc_box;              /* preconditioner Schwarza na boxach */
} asm_ctx;
 
int Preconditioner(void *pointer,Vec u0,Vec u1);

void Przygotuj_macierz_interpolacji(asm_ctx *ctx)
{
 int i,j,row,col,r,k,l;
 int mg=ctx->mg,m=ctx->m,rozmiar=ctx->rozmiar;
 
 Scalar val;
 MatCreate(MPI_COMM_WORLD,rozmiar*rozmiar,mg*mg,&ctx->I);
 for(j=0;j<mg;j++)
 for(i=0;i<mg;i++)
 {
  col=i+mg*j;
  row=r=(m-1)*rozmiar+m-1+i*m+j*m*rozmiar;
  val=1;
  MatSetValues(ctx->I,1,&row,1,&col,&val,INSERT_VALUES);
  for(k=1;k<m;k++)
  {
   val=(Scalar)(m-k)/m;
   for(l=0;l<6*k;l++)
   {
    if((l>1)&&(l<k))row=r-k*rozmiar+l;
    if((l>k-1)&&(l<2*k))row=r-k*rozmiar+k+(l-k)*rozmiar;
    if((l>2*k-1)&&(l<3*k))row=r+k+(l-2*k)*(rozmiar-11);
    if((l>3*k-1)&&(l<4*k))row=r+k*rozmiar-(l-3*k);
    if((l>4*k-1)&&(l<5*k))row=r+k*(rozmiar-1)-(l-4*k)*rozmiar;
    if((l>5*k-1)&&(l<6*k))row=r-k-(l-5*k)*(rozmiar-1);
    MatSetValues(ctx->I,1,&row,1,&col,&val,INSERT_VALUES);
   }
  }
 }
 MatAssemblyBegin(ctx->I,MAT_FINAL_ASSEMBLY);
 MatAssemblyEnd(ctx->I,MAT_FINAL_ASSEMBLY);
 MatCompress(ctx->I);
}

Przygotuj_preconditioner(asm_ctx *ctx,PC *pc_shell)
{
 int flag;
 int rozmiar=ctx->rozmiar,M=ctx->M;
 int zakl=ctx->zakl,mg=ctx->mg;
 PC pcg;
 KSP ksp;

 /*Przygotowanie preconditionera Schwarza */
 PCCreate(MPI_COMM_WORLD,&ctx->pc_box);
 PCSetType(ctx->pc_box,PCASM);
 PCASMCreateSubdomains2D(rozmiar,rozmiar,M,M,1,zakl,&i,&ctx->boxy);
 PCASMSetLocalSubdomains(ctx->pc_box,i,ctx->boxy);
 PCSetOperators(ctx->pc_box,A,A,SAME_PRECONDITIONER);
 PCSetVector(ctx->pc_box,u);
 PCASMSetOverlap(ctx->pc_box,0);
 PCSetUp(ctx->pc_box);

 /* gruba siatka */
 VecCreate(MPI_COMM_WORLD,PETSC_DECIDE,mg*mg,&ctx->fg);
 VecDuplicate(ctx->fg,&ctx->ug);
 Przygotuj_macierz_laplasjanu(&ctx->Ag,mg,mg);
 SLESCreate(MPI_COMM_WORLD,&ctx->slesg);
 SLESSetOperators(ctx->slesg,ctx->Ag,ctx->Ag,SAME_PRECONDITIONER);
 SLESGetPC(ctx->slesg,&pcg);
 PCSetType(pcg,PCLU);
 SLESGetKSP(ctx->slesg,&ksp);
 KSPSetType(ksp,KSPPREONLY);
 Przygotuj_macierz_interpolacji(ctx);
}

int main(int argc,char **args)
{
 KSP ksp;
 PC pc_shell;
 asm_ctx ams;
 double norma,emin,emax;
 int i,flag;
 PetscInitialize(&argc,&args,PETSC_NULL,help);
 ams.M=4;
 ams.mg=7;
 ams.zakl=1;
 ams.m=8;
 OptionsGetInt(PETSC_NULL,"M",&ams.M,&flag);
 OptionsGetInt(PETSC_NULL,"mg",&ams.mg,&flag);
 OptionsGetInt(PETSC_NULL,"m",&ams.m,&flag);
 OptionsGetInt(PETSC_NULL,"zakl",&ams.zakl,&flag);
 ams.rozmiar=(ams.mg+1)*ams.m-1;

 Przygotuj_zadanie(ams.rozmiar,&sles,&A,&u0,&u,&f);
 SLESGetKSP(sles,&ksp);
 SLESGetPC(sles,&pc_shell);
 Przygotuj_preconditioner(&ams,&pc_shell);
 PCSetType(pc_shell,PCSHELL);
 PCShellSetApply(pc_shell,Preconditioner,&ams);
 PCShellSetName(pc_shell,"asmbox");
 KSPSetType(ksp,KSPGMRES);
 KSPSetComputeSingularValues(ksp);

 SLESSetFromOptions(sles);
 Rozwiaz(sles);
 KSPComputeExtremeSingularValues(ksp,&emax,&emin);
 PetscFPrintf(MPI_COMM_WORLD,stderr," Uwarunkowanie  = %e\n\n",emax/emin);

 SLESDestroy(ams.slesg);
 MatDestroy(ams.Ag);
 VecDestroy(ams.ug);
 VecDestroy(ams.fg);
 MatDestroy(ams.I);
 Zwolnij_pamiec(sles,A,u0,u,f);
 PetscFinalize();
}

int Preconditioner(void *pointer,Vec v0 ,Vec v1)
{
 asm_ctx *ams=(asm_ctx*)pointer;
 int its;
 /*rozwiązanie na boxach */
 PCApply(ams->pc_box,v0,v1);
 /* rozwiazanie na grubej siatce */
 MatMultTrans(ams->I,v0,ams->fg);
 SLESSolve(ams->slesg,ams->fg,ams->ug,&its);
 VecScale(&val,ams->ug);
 MatMultAdd(ams->I,ams->ug,v1,v1);
 return(0);
}
 

Po uruchomieniu mamy:
hydra:/usr/home/bin/> przyklad8_4
 Liczba iteracji = 24
 Blad = 6.442081e-04
 Uwarunkowanie = 5.804242e+01
hydra:/usr/home/bin/>
Poniżej przytaczamy pliki źródłowe common.c i common.h, z których korzystaliśmy w niniejszym rozdziale.
common.h

#include "sles.h"
Vec u,u0,f;
Mat A;
SLES sles;
PC pc;
int N=100,its,i;
Scalar val;
double norma;


int Rozwiaz(SLES sles);
int Przygotuj_zadanie(int rozmiar,SLES *sles,Mat *A,Vec *u0,Vec *u,Vec *f);
int Zwolnij_pamiec(SLES sles,Mat A,Vec u0,Vec u,Vec f);
int Przygotuj_macierz_laplasjanu(Mat *A,int roz_x,int roz_y);





common.c #include "common.h" int Przygotuj_macierz_laplasjanu(Mat *A,int roz_x,int roz_y) {   Scalar val;  int n,m,i,j;  n=roz_x*roz_y;  MatCreateSeqAIJ(MPI_COMM_WORLD,n,n,5,PETSC_NULL,A);;  for(i=0;i<n;i++)  {   val=4;   MatSetValues(*A,1,&i,1,&i,&val,INSERT_VALUES);;      /* elementy na dalekich kodiagonalach */   val=-1;   j=i+roz_y;   if(j<n)   {    MatSetValues(*A,1,&i,1,&j,&val,INSERT_VALUES);;    MatSetValues(*A,1,&j,1,&i,&val,INSERT_VALUES);;   }     /* elementy na bliskich kodiagonalach */   j=i+1;   val=-1;   if(j<n)if(((i+1)%roz_x)!=0||(i==0))   {    MatSetValues(*A,1,&i,1,&j,&val,INSERT_VALUES);;    MatSetValues(*A,1,&j,1,&i,&val,INSERT_VALUES);;   }  }  MatAssemblyBegin(*A,MAT_FINAL_ASSEMBLY); ;  MatAssemblyEnd(*A,MAT_FINAL_ASSEMBLY); ;  MatCompress(*A);  return(0); } /*Przygotowanie zadania testowego */ int Przygotuj_zadanie(int N,SLES *sles,Mat *A,Vec *u0,Vec *u,Vec *f) {  PetscRandom rctx;  VecCreate(MPI_COMM_WORLD,PETSC_DECIDE,N*N,f);  VecDuplicate(*f,u);  VecDuplicate(*f,u0);  Przygotuj_macierz_laplasjanu(A,N,N);   SLESCreate(MPI_COMM_WORLD,sles);   SLESSetOperators(*sles,*A,*A,SAME_PRECONDITIONER);  PetscRandomCreate(MPI_COMM_WORLD,RANDOM_DEFAULT,&rctx);   VecSetRandom(rctx,*u0);   PetscRandomDestroy(rctx);   MatMult(*A,*u0,*f);  return(0); } /* Zwolnienie pamięci po zadaniu testowym */ int Zwolnij_pamiec(SLES sles,Mat A,Vec u0,Vec u,Vec f) {  SLESDestroy(sles);  VecDestroy(u0);  VecDestroy(u);   VecDestroy(f);   MatDestroy(A); } int Rozwiaz(SLES sles) {  SLESSetFromOptions(sles);  SLESSolve(sles,f,u,&its); /*Podanie wynikow*/  val=-1;  VecAXPY(&val,u,u0);  VecNorm(u0,NORM_2,&norma);    PetscFPrintf(MPI_COMM_WORLD,stderr,"\nLiczba iteracji = %i\n",its);   PetscFPrintf(MPI_COMM_WORLD,stderr," Blad           = %e\n",(double)norma); }
makefile (do skompilowania powyższych przykładów)
include $(PETSC_DIR)/bmake/$(PETSC_ARCH)/base
CC = gcc -DPETSC_LOG
CLINKER = gcc
CFLAGS = $(PCONF) $(PETSC_INCLUDE) $(BS_INCLUDE)
LIBBASE = libpetscsles libpetscsnes
OPTS = -O
BOPT = O

wszystko: przykład8_1 przykład8_2 przykład8_3 przykład8_4

przykład8_1: przyklad8_1.o  
             $(CLINKER) -o przyklad8_1 przyklad8_1.o  common.o        
                   $(PETSC_LIB)
             $(RM) przyklad8_1.o
przyklad8_2: przyklad8_2.o  
             $(CLINKER) -o przyklad8_2 przyklad8_2.o common.o        
                  $(PETSC_LIB)
              $(RM) przyklad8_2.o

przyklad8_3: przyklad8_3.o  
             $(CLINKER) -o przyklad8_3 przyklad8_3.o common.o        
                   $(PETSC_LIB)
              $(RM) przyklad8_3.o

przyklad8_4: przyklad8_4.o  
             $(CLINKER) -o przyklad8_4 przyklad8_4.o common.o        
                   $(PETSC_LIB)
              $(RM) przyklad8_4.o
Aby skompilować powyższe przykłady należy uruchomić program make z nazwą pliku, który chcemy skompilować, na przykład:
hydra:/usr/home/students/bojko/>make przyklad8_1
Plik wykonywalny otrzyma nazwę przyklad8_1, więc będziemy go uruchamiać, pisząc

hydra:/usr/home/students/bojko/>przyklad8_1

Można też od razu skompilować wszystkie programy (pod warunkiem, że wszystkie pliku źródłowe są w jednym katalogu) poleceniem:

hydra:/usr/home/students/bojko/>make wszystko
KSP, czyli iteracyjne metody rozwiązywania równań Spis treści Szczegółyo preconditionerze AMS, których nie znajdziecie w oficjalnymmanualu
 


Copyright (C) Marcin Bojko i Piotr Krzyzanowski, 1998.
Ostatnia modyfikacja: 12.X.98.