// Dieses Programm integriert die Diffusionsgleichung mittels
// ADI(Alternativ Direction Implicit)-Methode und gibt die Ergebnisse in
// Form von PNG-Grafiken aus, aus denen sich z.B. mit Programmen wie
// ffmpeg kleine Filme erzeugen lassen. Die Anzahl der ausgegebenen
// Bilder läßt sich über die Variable "fps" steuern (siehe unten). Für
// die lineare Algebra wird dabei die Bibliothek GLS (Gnu
// Scientific-Library) verwendet, weswegen mit "-ldislin -lgsl
// -lgslcblas" gelinkt werden muss.

#include <math.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_blas.h>

// Die folgende Zeile muss evtl. an Eure lokale Dislin-Installation
// angepasst werden.
#include "/usr/local/dislin/dislin.h"

// Eine nützliche Definition zum Runden
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

// Zeitintervall [t_0,t_0]
double t_0 = 0.0;
double t_1 = 1.0;
// Raumbereich [x_0,x_1]x[y_0,y_1]
double x_0 = 0.0;
double x_1 = 1.0;
double y_0 = 0.0;
double y_1 = 1.0;

// Raum- und Zeitschritt. Aus diesen Größen wird weiter unten die
// Anzahl der Schritte berechnet.
double dx = 0.02;
double dy = 0.02;
double dt = 0.001;

// Diffusionskonstante
double Diff = 0.1;

// fps ist die Anzahl der Bilder, die pro simulierter Sekunde ausgegeben werden sollen
int fps = 200;

// Die Funktionen startwert und startableitung werden zu Beginn des
// Programms verwendet, um die Anfangsbedingung festzulegen.
double startwert(int i, int j) {
  double y = i * dx;
  double x = j * dy;

  return exp(-20.0*pow(x-0.5,2)-20.0*pow(y-0.5,2));
}

int main () {
  // Die Anzahl der räumlichen und zeitlichen Iterationsschritte wird
  // aus der Größe der entsprechenden Intervalle und den Schrittweiten
  // berechnet.
  int x_steps = round( (x_1 - x_0) / dx); 
  int y_steps = round( (y_1 - y_0) / dy);
  int t_steps = round( (t_1 - t_0) / dt);

  printf("\nx_steps=%d y_steps=%d t_steps=%d\n",x_steps, y_steps, t_steps);

  // Die Variablen fname und titel werden für die Ausgabe mit Dislin
  // benötigt (siehe unten).
  char fname[80];
  char titel[80];

  // Je nach Wahl der globalen Variablen fps und dt wird die Zahl der
  // zeitlichen Iterationen berechnet, die zwischen zwei Ausgaben
  // durchgeführt werden sollen.
  int steps_pro_frame = round(1.0 / fps / dt);
  if ( !steps_pro_frame ) {
    steps_pro_frame = 1;
  }
  
  // Die Zeitintegration erfolgt durch Lösung eines linearen
  // Gleichungssystems. Im Folgenden werden die dafür benötigten Vektoren und
  // eine Matrix definiert.
  int vektorlaenge = (x_steps + 1)*(y_steps + 1);

  gsl_vector *u      = gsl_vector_alloc(vektorlaenge);
  gsl_vector *u_halb = gsl_vector_alloc(vektorlaenge);
  gsl_vector *u_temp = gsl_vector_alloc(vektorlaenge);
  gsl_vector *gamma  = gsl_vector_alloc(vektorlaenge);
  gsl_matrix *A      = gsl_matrix_alloc(vektorlaenge, vektorlaenge);
  gsl_matrix *B      = gsl_matrix_alloc(vektorlaenge, vektorlaenge);
  gsl_matrix *C      = gsl_matrix_alloc(vektorlaenge, vektorlaenge);
  gsl_matrix *D      = gsl_matrix_alloc(vektorlaenge, vektorlaenge);

  // Die folgenden drei Größen werden gsl-intern für die LU-Zerlegung
  // benötigt.
  gsl_permutation *p_A = gsl_permutation_alloc(vektorlaenge);
  gsl_permutation *p_C = gsl_permutation_alloc(vektorlaenge);
  int sign = 0;

  int i = 0; // i ist der x-Index
  int j = 0; // j ist der y-Index
  int k = 0; // k ist der Zeitindex

  // Division und Potenzierung ist relativ teuer, sollte deshalb nicht
  // öfter als nötig berechnet werden!
  double alpha = Diff * dt / 2.0 / dx / dx;
  double beta  = Diff * dt / 2.0 / dy / dy;

  // Das Feld wird entsprechend der Anfangsbedingung mit startwert()
  // initialisiert.
  for (i = 0; i < vektorlaenge; i++) {
      gsl_vector_set(u, i, startwert(i / (x_steps+1), i % (x_steps+1)));
  }

  // Erstelle die Matrix A
  for ( i=0; i<vektorlaenge; i++ ) {
    gsl_matrix_set(A, i, i, 1.0+2.0*alpha);

    if ( (i % (x_steps + 1) != 0) && (i % (x_steps + 1) != x_steps) ) {
      gsl_matrix_set(A, i, i-1, -alpha);
      gsl_matrix_set(A, i, i+1, -alpha);
    } else if ( i % (x_steps + 1) == 0 ) {
      gsl_matrix_set(A, i, i+1, -2.0*alpha);
    } else {
      gsl_matrix_set(A, i, i-1, -2.0*alpha);
    } 
  }

  // Erstelle die Matrix B
  for ( i=0; i<vektorlaenge; i++ ) {
    gsl_matrix_set(B, i, i, 1.0-2.0*beta);

    if ( (i > x_steps ) && (i < vektorlaenge - (x_steps + 1)) ) {
      gsl_matrix_set(B, i, i+x_steps+1, beta);
      gsl_matrix_set(B, i, i-x_steps-1, beta);
    } else if ( i <= x_steps ) {
      gsl_matrix_set(B, i, i+x_steps+1, 2.0*beta);
    } else {
      gsl_matrix_set(B, i, i-x_steps-1, 2.0*beta);
    } 
  }

 // Erstelle die Matrix C
  for ( i=0; i<vektorlaenge; i++ ) {
    gsl_matrix_set(C, i, i, 1.0+2.0*beta);

    if ( (i > x_steps ) && (i < vektorlaenge - (x_steps + 1)) ) {
      gsl_matrix_set(C, i, i+x_steps+1, -beta);
      gsl_matrix_set(C, i, i-x_steps-1, -beta);
    } else if ( i <= x_steps ) {
      gsl_matrix_set(C, i, i+x_steps+1, -2.0*beta);
    } else {
      gsl_matrix_set(C, i, i-x_steps-1, -2.0*beta);
    } 
  }

  // Erstelle die Matrix D
  for ( i=0; i<vektorlaenge; i++ ) {
    gsl_matrix_set(D, i, i, 1.0-2.0*alpha);

    if ( (i % (x_steps + 1) != 0) && (i % (x_steps + 1) != x_steps) ) {
      gsl_matrix_set(D, i, i-1, alpha);
      gsl_matrix_set(D, i, i+1, alpha);
    } else if ( i % (x_steps + 1) == 0 ) {
      gsl_matrix_set(D, i, i+1, 2.0*alpha);
    } else {
      gsl_matrix_set(D, i, i-1, 2.0*alpha);
    } 
  }

  // Führe zunächst die LU-Zerlegung der Matrizen A und C durch. Dieser
  // Teil des Programms nimmt die meiste Zeit in Anspruch.
  printf("\nLU-Zerlegung von A beginnt!\n");
  gsl_linalg_LU_decomp(A, p_A, &sign);
  printf("\nLU-Zerlegung von C beginnt!\n");
  gsl_linalg_LU_decomp(C, p_C, &sign);
  printf("\nLU-Zerlegung beendet!\n");

  for ( k = 0; k < t_steps; k++ ) {

    //---{ Ausgabe mit Dislin }-----------------------------------------
    if ( k % steps_pro_frame == 0) {
      scrmod("REVERSE");
      metafl("PNG");
      filmod("DELETE");
      sprintf(fname, "ADI%d.png", round(k / steps_pro_frame) );
      setfil(fname);
      disini();
      texmod("ON");
      
      sprintf(titel, "%d Stuetzstellen, $t=%f$",x_steps + 1, t_0 + (k + 1) * dt);
      titlin(titel, 4);

      graf3d(x_0, x_1, x_0, (x_1 - x_0)/10.0, y_0, y_1, y_0, (y_1 -
            y_0)/10.0, -0.2, 1.0, -0.2, 0.2);
      title();

      double masse = 0.0;
      for (i = 0; i<vektorlaenge; i++) {
        masse += gsl_vector_get(u, i); 
      }
      printf("\n Masse: %f",masse);

      float darst[y_steps + 1][x_steps + 1];
      for (i = 0; i<=y_steps; i++) {
        for (j = 0; j<=x_steps; j++) {
	        darst[i][j] = gsl_vector_get(u, i * (x_steps+1) + j);
	      }
      }

      surmat((float *) darst, y_steps + 1, x_steps + 1, 1, 1);

      disfin();
    }
    //------------------------------------------------------------------

    //---{ Der eigentliche Zeitschritt }-------------------------------
    gsl_blas_dgemv(CblasNoTrans, 1.0, B, u, 0.0, u_temp);
    gsl_linalg_LU_solve(A, p_A, u_temp, u_halb);

    gsl_blas_dgemv(CblasNoTrans, 1.0, D, u_halb, 0.0, u_temp);
    gsl_linalg_LU_solve(C, p_C, u_temp, u);
    //-----------------------------------------------------------------
  }
  
  // Zuletzt kann der reservierte Speicher wieder freigegeben werden.
  gsl_vector_free(u);
  gsl_vector_free(u_halb);
  gsl_vector_free(u_temp);
  gsl_matrix_free(A);
  gsl_matrix_free(B);
  gsl_matrix_free(C);
  gsl_matrix_free(D);

  return 1;
}