/**
 **
 ** CholeskyFloat.java - Single-precision Cholesky decomposition
 **
 ** Copyright (C) 2025 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  CholeskyFloat.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.common;

import Jama.Matrix;

// For testing for future GPU - is float enough
public class CholeskyFloat {
	   private float[][] L;
	   private int n;
	   
	   public CholeskyFloat (Matrix mA) {
		   double [][] M = mA.getArray();
		   float [][] A = new float [M.length] [M[0].length];
		   for (int i = 0; i < A.length; i++) {
			   for (int j = 0; j < A[i].length; j++) {
				   A[i][j] = (float) M[i][j];
			   }
		   }
		   setupCholeskyFloat(A);
	   }
	  
		public Matrix getL() {
			   double [][] dL = new double [n] [n];
			   for (int i = 0; i < n; i++) {
				   for (int j = 0; j < n; j++) {
					   dL[i][j] = L[i][j];
				   }
			   }
			return new Matrix(dL,n,n);
		}
	   
	   
	   public CholeskyFloat (float[][] A) {
		   setupCholeskyFloat(A);
	   }
	   
	   public void setupCholeskyFloat (float[][] A) {
		   // Initialize.
		   n = A.length;
		   L = new float[n][n];
		   // Main loop.
		   for (int j = 0; j < n; j++) {
			   float[] Lrowj = L[j];
			   float d = 0;
			   for (int k = 0; k < j; k++) {
				   float[] Lrowk = L[k];
				   float s = 0;
				   for (int i = 0; i < k; i++) {
					   s += Lrowk[i]*Lrowj[i];
				   }
				   Lrowj[k] = s = (A[j][k] - s)/L[k][k];
				   d = d + s*s;
			   }
			   d = A[j][j] - d;
			   L[j][j] = (float) Math.sqrt(Math.max(d,0));
			   for (int k = j+1; k < n; k++) {
				   L[j][k] = 0;
			   }
		   }
		   return;
	   }


	   public Matrix solve (Matrix B) {
		   double [][] M = B.getArray();
		   float [][] b = new float [M.length] [M[0].length];
		   for (int i = 0; i < b.length; i++) {
			   for (int j = 0; j < b[i].length; j++) {
				   b[i][j] = (float) M[i][j];
			   }
		   }
		   float  [][] x = solve (b);
		   double [][] d = new double [M.length] [M[0].length];
		   for (int i = 0; i < x.length; i++) {
			   for (int j = 0; j < x[i].length; j++) {
				   d[i][j] = x[i][j];
			   }
		   }
		   return new Matrix(d,n,x[0].length);		   
	   }
	   
	   public float[][] solve (float [][] b) {
		   if (b.length != n) {
			   throw new IllegalArgumentException("Matrix row dimensions must agree.");
		   }
		   // Copy right hand side.
		   int nx = b[0].length;
		   float [][] x = new float [n][];
		   for (int i = 0; i < n; i++) {
			   x[i] = b[i].clone();
		   }

		   // Solve L*Y = B;
		   for (int k = 0; k < n; k++) {
			   for (int j = 0; j < nx; j++) {
				   for (int i = 0; i < k ; i++) {
					   x[k][j] -= x[i][j]*L[k][i];
				   }
				   x[k][j] /= L[k][k];
			   }
		   }
		   // Solve L'*X = Y;
		   for (int k = n-1; k >= 0; k--) {
			   for (int j = 0; j < nx; j++) {
				   for (int i = k+1; i < n ; i++) {
					   x[k][j] -= x[i][j]*L[i][k];
				   }
				   x[k][j] /= L[k][k];
			   }
		   }
		   return x;
	   }

}
