/* 
 * CG 2x2 case with potitive definite assumption 
 * based on Cholesky decomposition.
 * From wiki: https://en.wikipedia.org/wiki/Cholesky_decomposition
 * The matrix M is positive definite if and only if there exists 
 * a unique lower triangular matrix L, with real and strictly 
 * positive diagonal elements, such that M = LL*. 
 */

#include <civlc.cvh>
#include <stdio.h>
#define n 2

$input double a1,a2,a3;
$input double b[n];
double x[n];
double xcg[n];

void cg(double A[n][n], double b[n], double x[n]) {
  double r[n]; //residual
  double p[n]; //search direction
  double temp[n];
  double temp2[n];
  double rsold; //<r, r> 
  double rsnew;
  double alpha;
  double beta;
  
  for (int i=0; i<n; i++) x[i] = 0;
  for (int i=0; i<n; i++) {
    temp[i] = 0.0;
    for(int j=0; j<n; j++) temp[i] += A[i][j]*x[j];
  }
  for (int i=0; i<n; i++) r[i] = b[i] - temp[i];
  rsold = 0.0;
  for (int i=0; i<n; i++) rsold += r[i]*r[i];
  for (int i=0; i<n; i++) p[i] = r[i];
  for (int k=0; k<n; k++) {
    for (int i=0; i<n; i++) {
      temp[i] = 0.0;
      for (int j=0; j<n; j++) temp[i] += A[i][j]*p[j];
    }
    alpha = 0.0;
    for (int i=0; i<n; i++) alpha += p[i]*temp[i];
    $assume(alpha != 0);
    alpha = rsold/alpha;
    // temp2 = r-alpha*temp
    for (int i=0; i<n; i++) temp2[i] = r[i] -alpha*temp[i];
    // r = temp2
    for (int i=0; i<n; i++) r[i] = temp2[i];
    // temp2 = x+alpha*p
    for (int i=0; i<n; i++) temp2[i] = x[i] +alpha*p[i];
    // x = temp2
    for (int i=0; i<n; i++) x[i] = temp2[i];
    if (k<n-1) {
      // rsnew = <r,r>
      rsnew = 0.0;
      for (int i=0; i<n; i++) rsnew += r[i]*r[i];
      $assume(rsold !=0);
      beta = rsnew/rsold;
      for (int i=0; i<n; i++) temp2[i] = r[i] +beta*p[i];
      // p = temp2
      for (int i=0; i<n; i++) p[i] = temp2[i];
      rsold = rsnew;
    }
  }
}

void main() {
  double Ax[n]; // result of A multiply x after get solution x
  double L[n][n]; //lower triangular matrix
  double LT[n][n];//its transpose
  double A[n][n];

  L[0][0] = a1;
  L[0][1] = 0;
  L[1][0] = a2;
  L[1][1] = a3;
  
  LT[0][0] = a1;
  LT[0][1] = a2;
  LT[1][0] = 0;
  LT[1][1] = a3;

  for (int i=0; i<n; i++) {
    for (int j=0; j<n; j++) {
      A[i][j] = 0.0;
      for (int k=0; k<n; k++) {
	A[i][j] += L[i][k] * LT[k][j]; //form the input matrix A
      }                                // to ensure A is Positive Definite
    }
  }
  
  $assume(b[0]!=0 || b[1]!=0);
  
  cg(A,b,xcg);
  for (int i=0; i<n; i++) {
    printf("x[%d] = %f\n\n",i, xcg[i]);
  }
  for (int i=0; i<n; i++) {
    Ax[i] = 0;
    for (int j=0; j<n; j++)
      Ax[i] += A[i][j]*xcg[j];
    $assert(Ax[i] == b[i]);
  }
}

