/**
 **
 ** CholeskyBlockTest.java - Unit tests for block Cholesky decomposition
 **
 ** Copyright (C) 2025 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  CholeskyBlockTest.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.CholeskyDecomposition;
import Jama.Matrix;
import ij.ImagePlus;

public class CholeskyBlockTest {
	public static void testCholesky(ImagePlus imp_src) {
		float [] fpixels = (float[]) imp_src.getProcessor().getPixels();
		int      width = imp_src.getWidth();
		int      height = imp_src.getHeight();
		int n = height;
		double [][] a = new double [n][n];
		double [][] b = new double [n][1];
		for (int i = 0; i < n; i++) {
			for (int j= 0; j < n; j++) {
				a[i][j] = fpixels[i*width+j];
			}
			b[i][0] = fpixels[i*width+n];
		}
		testCholesky(
				new Matrix(a), // Matrix wjtjlambda,
				new Matrix(b), // Matrix jty)
				imp_src.getTitle()); // String title);
	}

	public static Matrix[] testCholesky(
			Matrix wjtjlambda_in,
			Matrix jty_in,
			String title) {
		int block_size = 70; // 100;//70~best //  64; // 60; // 70; // 80; // 120; // 150; // 200; // 100; // 4; // 10; // 100; // 28;
		boolean truncate = false;
		int trunc_size = 199; // 0 to use full size
		Matrix wjtjlambda,jty;
		if (truncate) {
			int tr_size = (trunc_size==0)?(((wjtjlambda_in.getRowDimension())/block_size) * block_size):trunc_size;
			wjtjlambda=wjtjlambda_in.getMatrix (
					0, // int i0,
					tr_size-1, // int i1,
					0, // int j0,
					tr_size-1); //int j1)
			jty=jty_in.getMatrix (
					0, // int i0,
					tr_size-1, // int i1,
					0, // int j0,
					0); //int j1)
		} else {
			wjtjlambda=wjtjlambda_in;
			jty = jty_in;
		}
		String dbg_title=title+"ch_diff_choleskyBlock-choleskyDecomposition-"+block_size+(truncate?("-truncate"+trunc_size):"");
		Matrix wjtjlambda_copy1 = new Matrix(wjtjlambda.getArrayCopy());
		Matrix wjtjlambda_copy = new Matrix(wjtjlambda.getArrayCopy());
		Matrix wjtjlambda_copy2 = new Matrix(wjtjlambda.getArrayCopy());
		int matrix_size = wjtjlambda.getRowDimension();
		double [] starts = new double[6];
		double start_time = (((double) System.nanoTime()) * 1E-9);
		CholeskyDecomposition choleskyDecomposition = new CholeskyDecomposition(wjtjlambda_copy);
		starts[0] = (((double) System.nanoTime()) * 1E-9) - start_time;
		CholeskyBlock choleskyBlock  = new CholeskyBlock(wjtjlambda_copy1.getArray(),block_size);		
		starts[1] = (((double) System.nanoTime()) * 1E-9) - start_time;
		Matrix mdelta_cholesky = choleskyDecomposition.solve(jty);
		starts[2] = (((double) System.nanoTime()) * 1E-9) - start_time;
		Matrix mdelta_cholesky_block =  choleskyBlock.solve(jty);
		starts[3] = (((double) System.nanoTime()) * 1E-9) - start_time;
		CholeskyFloat choleskyFloat  = new CholeskyFloat(wjtjlambda_copy2);
		starts[4] = (((double) System.nanoTime()) * 1E-9) - start_time;
		Matrix mdelta_cholesky_float =  choleskyFloat.solve(jty);
		starts[5] = (((double) System.nanoTime()) * 1E-9) - start_time;
		
		System.out.println("testCholesky(): matrix size=            "+matrix_size);
		System.out.println("testCholesky(): block_size=             "+block_size);
		System.out.println("testCholesky(): choleskyDecomposition:  "+(starts[0])+" sec");
		System.out.println("testCholesky(): CholeskyBlock():        "+(starts[1]-starts[0])+" sec");
		System.out.println("testCholesky(): cholesky.solve():       "+(starts[2]-starts[1])+" sec");
		System.out.println("testCholesky(): block.solve():          "+(starts[3]-starts[2])+" sec");
		System.out.println("testCholesky(): CholeskyFloat():        "+(starts[4]-starts[3])+" sec");
		System.out.println("testCholesky(): float.solve():          "+(starts[5]-starts[4])+" sec");
		
		System.out.println("testCholesky(): title=                  "+title);
		System.out.println("testCholesky(): dbg_title=              "+dbg_title);

		/*
		Matrix ch_diff =      choleskyBlock.getL().minus(choleskyDecomposition.getL());
		double [][] dbg_img = {
				choleskyBlock.getL().getRowPackedCopy(),
				choleskyDecomposition.getL().getRowPackedCopy(),
				ch_diff.getRowPackedCopy()};
		String[] dbg_titles = {"choleskyBlock","choleskyDecomposition",
				"choleskyBlock-choleskyDecomposition"};
				*/
		Matrix ch_diff =      choleskyFloat.getL().minus(choleskyDecomposition.getL());
		double [][] dbg_img = {
				choleskyFloat.getL().getRowPackedCopy(),
				choleskyDecomposition.getL().getRowPackedCopy(),
				ch_diff.getRowPackedCopy()};
		String[] dbg_titles = {"choleskyBlock","choleskyDecomposition",
				"choleskyBlock-choleskyDecomposition"};
		
		ShowDoubleFloatArrays.showArrays(
				dbg_img, // double[] pixels,
				ch_diff.getRowDimension(), // int width,
				ch_diff.getRowDimension(), // int height,
				true,
				dbg_title, // String title)
				dbg_titles);
		return new Matrix[] {mdelta_cholesky, mdelta_cholesky_block,mdelta_cholesky_float};
		/*
		    First run, next MULTITHREADED ones are faster. Ran on an 8-core x86, 16 threads
			choleskyBlockMulti() last pass n= 33, tile_diag=32
			testCholesky(): matrix size=            2258
			testCholesky(): block_size=             70
			testCholesky(): choleskyDecomposition:  1.7590559809468687 sec   Jama CholeskyDecomposition, single-threaded
			testCholesky(): CholeskyBlock():        0.34954120498150587 sec  FIRST run is faster (5x) than single-threaded, but slower than next
			testCholesky(): cholesky.solve():       0.030882437014952302 sec Jama CholeskyDecomposition.solve()
			testCholesky(): block.solve():          0.051133116940036416 sec FIRST run is slower than single-threaded!
			testCholesky(): title=                  spd_a_2.tif
			testCholesky(): dbg_title=              spd_a_2.tifch_diff_choleskyBlock-choleskyDecomposition-70

			DEBUG_LEVEL = 1, CLT_PARAMETERS.lwir.getDebugLevel() = 0 LOG_LEVEL=ERROR LOG_LEVEL_SET=false
			choleskyBlockMulti() last pass n= 33, tile_diag=32
			testCholesky(): matrix size=            2258
			testCholesky(): block_size=             70
			testCholesky(): choleskyDecomposition:  1.7251908951438963 sec
			testCholesky(): CholeskyBlock():        0.19312086096033454 sec Second run, 9x acceleration
			testCholesky(): cholesky.solve():       0.03081041993573308 sec
			testCholesky(): block.solve():          0.005443983944132924 sec Second run, 5.7x acceleration
			testCholesky(): title=                  spd_a_2.tif
			testCholesky(): dbg_title=              spd_a_2.tifch_diff_choleskyBlock-choleskyDecomposition-70
		 */
		
		
	}
}
